Shopify Theme Engineering Portal
The engineering handbook for this Shopify Theme Store project — not the theme itself, but the standard every human developer and every AI coding tool (Cursor, Claude Code, ChatGPT, Codex, GitHub Copilot, Gemini, Windsurf, and whatever ships next) follows to produce code indistinguishable from one disciplined senior engineer's work.
Project Vision
What we are building, and the standard every line of code is measured against.
Overview
We are building a new Shopify Theme Store product from a blank canvas. The theme itself is not the deliverable of this document — this portal is. It is the engineering constitution that every human and every AI coding assistant reads before writing a single line of Liquid, CSS, or JavaScript for this repository.
The premise is simple: multiple developers, using different AI tools (Cursor, Claude Code, ChatGPT, Codex, GitHub Copilot, Gemini, Windsurf, and whatever ships next), must all produce code that looks like it came from one disciplined senior engineer. The tool changes. The standard does not.
Why It Matters
Without a shared, written standard, every AI tool defaults to its own house style — different naming, different indentation philosophy, different assumptions about what "good" Liquid looks like. The result is a codebase that reads like it was stitched together by five different contractors, because it effectively was. This portal exists to collapse that variance to zero before it starts.
Best Practices
- Treat this portal as the single source of truth — if a rule lives only in someone's head or a Slack thread, it does not exist yet.
- Every new engineering decision gets written here before it is enforced, not after a bug reveals the gap.
- Vision statements should be falsifiable — "fast" is not a vision, "Lighthouse Performance ≥ 90 on mobile product pages" is.
- Revisit this section at the start of every quarter; a vision that never gets re-read has quietly become decoration.
Common Mistakes
- Writing a vision that is really a feature list — features change constantly, principles should not.
- Letting the vision live in a pitch deck that developers never open.
- Treating "we'll figure it out as we build" as a strategy instead of a risk.
- Confusing "aspirational" with "actionable" — a vision statement without validation criteria cannot be enforced.
Examples
Team Rules
- This document is versioned in the repository, not in a wiki or a deck that can drift out of sync with the code.
- Any change to project vision requires a PR against this file, reviewed by the tech lead.
- AI tools are instructed to read this portal before generating code, not after being corrected.
- The vision defines trade-offs explicitly (e.g. performance over visual flourish) so AI does not have to guess which wins.
AI Instructions
- Before generating any theme code, confirm the request aligns with the stated vision; flag conflicts instead of silently complying.
- Never invent product scope ("I'll also add a mega-menu") that was not requested or documented here.
- When a prompt is ambiguous relative to this vision, ask a clarifying question rather than picking a default.
- Cite the relevant portal section in code comments or PR descriptions when a non-obvious decision was vision-driven.
Validation Checklist
Project Goals
The measurable targets that turn the vision into acceptance criteria.
Overview
Goals translate the vision into numbers an AI tool and a human reviewer can both check against. Where the vision says "fast," goals say LCP < 2.5s on mobile product pages, Lighthouse Performance ≥ 60. Where the vision says "consistent," goals say 0 Theme Check errors, 0 ESLint errors on merge to main.
Why It Matters
Unmeasurable goals produce unreviewable pull requests. A reviewer — human or AI — cannot approve or reject work against a vague target. Concrete goals are what let us automate the boring 80% of review (lint, Theme Check, Lighthouse CI) and reserve human judgment for the 20% that actually needs it: architecture, UX, taste.
| Category | Target | Enforced by |
|---|---|---|
| Performance | Lighthouse Performance ≥ 60 mobile / ≥ 90 desktop | Lighthouse CI, PR gate |
| Accessibility | Lighthouse Accessibility ≥ 90, WCAG 2.1 AA | axe-core CI, manual QA |
| Code quality | 0 Theme Check errors, 0 ESLint / Stylelint errors | GitHub Actions |
| Consistency | 100% of sections use theme blocks + schema presets | AI + human PR review |
| Theme Store | Meets all Shopify Theme Store submission requirements | Pre-submission checklist |
| Delivery | Every merged PR traces to a written requirement | PR template + checklist |
Best Practices
- State every goal as a number with a measurement method, not an adjective.
- Separate launch-blocking goals (must hit before v1) from ongoing goals (track forever, e.g. performance budgets).
- Assign an owner and a CI check to each goal — a goal nobody enforces is a wish.
- Re-baseline goals when Shopify's own platform requirements change (Theme Store rules evolve over time).
Common Mistakes
- Setting goals that conflict without acknowledging the trade-off (e.g. "add rich animation" + "LCP < 2s" without prioritizing one).
- Copying generic industry benchmarks without validating they fit this merchant's catalog size and traffic profile.
- Treating goals as a one-time kickoff exercise instead of a living scoreboard checked every sprint.
- Letting a goal exist without an automated check, so it silently erodes over months of feature work.
Examples
Team Rules
- Every goal in this section must have a CI check or a manual QA step tied to it before it counts as "enforced."
- Goals are reviewed at the end of every sprint against real Lighthouse / Theme Check output, not assumptions.
- Conflicting goals are resolved explicitly in writing (e.g. "performance wins over decorative motion") — never left implicit.
- No PR merges that regresses a launch-blocking goal, regardless of who or what authored the change.
AI Instructions
- When implementing a feature, check whether it risks any stated goal (bundle size, LCP, accessibility score) and flag it in the PR description.
- Do not silently trade one goal for another (e.g. adding a carousel that hurts LCP) — surface the trade-off for human decision.
- Reference the specific goal a change supports when the connection is not obvious from the diff alone.
- If a requested feature has no stated goal it maps to, ask whether one should be added rather than building blind.
Validation Checklist
Theme Architecture
How Online Store 2.0, sections, and theme blocks fit together in this codebase.
Overview
This theme is built on Shopify's Online Store 2.0 architecture: JSON templates that compose sections, sections that compose blocks, and blocks that are themselves reusable across sections via theme blocks. Nothing merchant-facing is hard-coded into a template file — everything is a section or block controlled through the Theme Editor.
Shopify's reference theme, Dawn, is our architectural baseline. We diverge from Dawn deliberately and document every divergence — we do not diverge by accident because an AI tool defaulted to a different pattern.
Why It Matters
Architecture is the one layer that is expensive to fix retroactively. A wrong naming convention costs minutes to fix; a wrong architecture (e.g. hard-coded homepage markup instead of JSON-templated sections) costs a rebuild. Defining it up front, and requiring every AI tool to work within it, prevents 40 sections of inconsistent structure from being generated before anyone notices.
Best Practices
- Every page is a JSON template (
templates/*.json) referencing sections — never a.liquidtemplate with inline markup. - Build with theme blocks (
"type": "@theme"/ nested blocks) so merchants can reorder content freely, not just toggle visibility. - Keep
layout/theme.liquidminimal: doctype, head, header/footer includes, and the content_for_layout — no business logic. - Co-locate a section's CSS/JS as scoped assets loaded only on templates that use it, never globally unless truly global (e.g. cart drawer).
Common Mistakes
- Hard-coding homepage content directly in
index.liquidinstead of composing it from reusable sections. - Using deeply nested snippet-calls-snippet-calls-snippet chains that are impossible for a reviewer (or AI) to trace.
- Duplicating a section's logic into three near-identical sections instead of parameterizing one with schema settings.
- Loading every section's CSS/JS on every page regardless of whether that section is present, bloating the critical path.
Examples
sections/featured-collection.liquid
{% schema %}
{
"name": "Featured collection",
"settings": [{ "type": "collection", "id": "collection", "label": "Collection" }],
"blocks": [{ "type": "@theme" }],
"presets": [{ "name": "Featured collection" }]
}
{% endschema %}templates/index.liquid
<div class="homepage-hero">
<h1>Welcome to Our Store</h1> {/} hard-coded, merchant can't edit
<img src="{{ 'hero.jpg' | asset_url }}">
</div>Team Rules
- No merchant-facing content or copy is hard-coded in a template or layout file — it lives in section schema settings.
- Every new section ships with a
{% schema %}block including at least one preset. - Reusable UI (buttons, price display, media) is a snippet; reusable, mergeable content units are a theme block.
- Architectural deviations from Dawn require a short written rationale in
/docs/architecture-decisions.md. - Any new top-level folder under the theme root requires sign-off — the structure in Folder Structure is authoritative.
AI Instructions
- Default to Online Store 2.0 patterns (JSON templates + sections + theme blocks) unless the task explicitly requires a Liquid template.
- When generating a new section, always include a complete, valid
{% schema %}with settings, blocks, and at least one preset. - Never inline styles or scripts in Liquid markup — emit to the section's scoped asset file instead.
- If a request would require deviating from this architecture, explain the trade-off before implementing rather than silently complying.
Validation Checklist
Folder Structure
The production-ready, Theme Store-compliant repository layout.
Overview
Every AI tool and every developer creates files in exactly these folders, using exactly these names. A file placed in the wrong folder is a code review blocker, not a style nitpick — Shopify's theme runtime resolves files by folder convention, so a misplaced file can silently fail to load.
Why It Matters
Shopify themes are not free-form applications; the CLI, the Theme Editor, and Shopify's own theme check tooling all assume this exact folder contract. Deviating from it breaks Theme Store submission and confuses every AI tool that has been trained on standard theme structure — consistency here is free, and inconsistency is expensive.
Best Practices
assets/stays flat — Shopify CDN does not support subdirectories, so use naming prefixes (section-hero.css) instead of nested folders.- Every UI string lives in
locales/en.default.jsonand is pulled via{{ 'key' | t }}— never hard-coded English in Liquid. - Global, cross-page settings (color palette, typography scale) live in
settings_schema.json; anything page- or section-specific lives in that section's own schema. utilities/holds tooling that never ships to the storefront (Stylelint config sources, design tokens, build scripts) — keep it out of the deployed theme package.
Common Mistakes
- Creating subfolders inside
assets/— Shopify will not resolve them; everything must be flat. - Hard-coding English copy into a section or snippet instead of routing it through
locales/, breaking localization. - Editing
config/settings_data.jsonby hand in a PR — it is generated by merchant activity in the Theme Editor, not by engineers. - Dumping one-off scripts or notes into the theme root instead of
utilities/or repository docs.
Examples
snippets/icon.liquid — one definition, five call sites, zero duplication.Team Rules
- No new top-level folder is created without updating this section and getting tech-lead sign-off.
assets/file names are prefixed by the section/component they belong to (section-hero.js, nothero.js) to avoid collisions in the flat namespace.- Every hard-coded string an AI generates in Liquid is treated as a bug until it is routed through
locales/. config/settings_data.jsonis never hand-edited in a PR — onlysettings_schema.jsonis.
AI Instructions
- Place every generated file in the folder defined here — never invent a new folder or nest folders inside
assets/. - When generating any user-facing copy, emit a locale key + value pair in
locales/en.default.jsoninstead of inlining the string. - Name new asset files with a component prefix to prevent collisions in the flat
assets/namespace. - Never write to or suggest edits to
settings_data.json— treat it as generated, merchant-owned state.
Validation Checklist
Technology Stack
What we build with — and, just as importantly, what we deliberately leave out.
Overview
The theme is built on Shopify Liquid, semantic HTML5, plain CSS with custom properties, and vanilla (or minimally-framework) JavaScript delivered as native ES modules. No build-time UI framework renders storefront markup — Shopify's runtime expects server-rendered Liquid, and fighting that with a client-side framework adds complexity and performance cost for no merchant benefit.
Why It Matters
Every dependency is a liability an AI tool has to reason about correctly, a security surface, and a Theme Store compliance risk (pre-minified or obfuscated dependencies are restricted). A small, well-understood stack means AI-generated code is more likely to be correct on the first try, because there is less surface area for it to get wrong.
| Layer | Choice | Rationale |
|---|---|---|
| Templating | Shopify Liquid | Required by the platform; server-rendered, cacheable, no hydration cost. |
| Styling | Plain CSS + custom properties | Theme Store requires plain CSS (no committed SCSS); custom properties give us design tokens without a preprocessor. |
| Scripting | Vanilla JS, native Web Components where useful | No framework hydration tax; progressive enhancement stays trivial. |
| Build tooling | Shopify CLI + esbuild-based bundling | Official tooling, fast, minimal config drift between developer machines. |
| Package management | npm, lockfile committed | Reproducible installs across every contributor and every AI tool's sandbox. |
| Linting | Theme Check, ESLint, Stylelint, Prettier | Automated, objective enforcement of every standard in this portal. |
Best Practices
- Prefer platform-native browser APIs (
<dialog>,IntersectionObserver, CSS:has()) over adding a library for a single use case. - If a dependency is genuinely needed, it must be small, actively maintained, and justified in writing in the PR.
- Ship JS as native ES modules (
type="module") so the browser handles dependency graphs — no bespoke module bundler magic at runtime. - Pin exact dependency versions in
package-lock.json; never let AI tools "helpfully" bump majors in an unrelated PR.
Common Mistakes
- Reaching for a heavy client-side framework because it is what an AI tool defaults to from its training data, not because the theme needs it.
- Adding a date/animation/carousel library for one component instead of writing 40 lines of vanilla JS.
- Committing pre-minified or obfuscated third-party code, which violates Theme Store code-quality requirements.
- Letting dependency versions drift silently across contributors because the lockfile was not committed or was overwritten.
Examples
Team Rules
- No UI framework (React, Vue, Svelte, etc.) renders storefront-facing markup — Liquid is the templating layer, full stop.
- Any new npm dependency requires a one-line justification in the PR description and tech-lead approval.
package-lock.jsonis committed and never manually edited.- Third-party code is never minified/obfuscated in the repo — only build output is minified, and only via our own build pipeline.
AI Instructions
- Do not introduce a new npm package to solve a problem solvable in under ~50 lines of vanilla JS or native CSS.
- Never generate React/Vue/Svelte component syntax for storefront markup — always emit Liquid + vanilla JS.
- If a task seems to require a new dependency, state the trade-off explicitly and ask before adding it to package.json.
- Match existing patterns in the codebase (e.g. how media loading or forms are already handled) rather than introducing a second competing pattern.
Validation Checklist
Development Workflow
The path every feature takes from Figma frame to production release.
Overview
Every feature — regardless of which developer or which AI tool builds it — passes through the same fourteen stages. No stage is optional, and no stage can be skipped because "the AI already checked it."
Why It Matters
A shared pipeline is what makes multi-tool, multi-developer output converge on one quality bar. If Cursor-authored code and Claude Code-authored code both pass through identical lint, Theme Check, accessibility, and human review gates, they arrive at production in the same condition — regardless of how they got there.
Best Practices
- Never jump from Figma straight to AI code generation — Component Analysis and Planning are what give the AI a correct, scoped prompt instead of a guess.
- Run Lint and Theme Check locally (pre-commit hook) before opening a PR, so GitHub Checks are a confirmation, not a discovery.
- Treat Human Review as a distinct stage from PR Review — the first is the author self-checking against this portal, the second is a teammate checking against the requirement.
- Keep each stage's output as an artifact (prompt text, Theme Check report, Lighthouse report) attached to the PR for traceability.
Common Mistakes
- Skipping Component Analysis and asking an AI tool to "build the homepage" from a Figma link with no breakdown — it will guess at architecture.
- Treating AI Code Generation as the finish line instead of the midpoint of a fourteen-stage pipeline.
- Merging before GitHub Checks finish because "it looked fine locally."
- Doing Performance and Accessibility checks only right before a release instead of on every PR, letting regressions compound silently.
Examples
Team Rules
- Every PR description names which of the fourteen stages were completed, with links to lint/Theme Check/Lighthouse output.
- No stage is skipped for "small" changes — small changes are exactly where undetected drift accumulates fastest.
- AI Code Generation output is never merged without a Human Review pass by the engineer who prompted it.
- Workflow stages map 1:1 to the Pull Request Checklist — see Pull Request Checklist.
AI Instructions
- When asked to implement a feature, first ask for or infer the Component Analysis and Planning output before generating final code.
- State clearly in your response which workflow stage you are operating in, so the human knows what is still pending.
- Never claim a change is "production ready" — that determination belongs to the Human Review and PR Review stages.
- Surface likely Lint, Theme Check, Accessibility, or Performance issues proactively rather than waiting for CI to catch them.
Validation Checklist
Figma to Shopify Process
Turning a static design file into a merchant-editable, schema-driven section.
Overview
A Figma frame is a snapshot of one possible content state, not a specification. Before any code is written, every frame is decomposed into: reusable components, dynamic vs. fixed content, responsive behavior across breakpoints, and the schema settings a merchant will need to reproduce or vary that state.
Why It Matters
Shopify sections must survive content the designer never imagined — a product with a 200-character title, an empty collection, a merchant who removes every block but one. A literal pixel-for-pixel translation of a Figma frame breaks the moment real data or merchant customization touches it. This process exists to catch that before code is written, not after a merchant reports a bug.
Best Practices
- Ask design for empty-state and max-content-state variants up front rather than inferring them from a single "ideal data" frame.
- Match Figma auto-layout spacing to real CSS custom properties (spacing scale) instead of copying arbitrary pixel values per component.
- Identify components that already exist in the theme before generating anything new — reuse over recreation.
- Confirm breakpoints with design (mobile/tablet/desktop) rather than assuming Dawn's default breakpoints apply.
Common Mistakes
- Hard-coding the exact copy and image from the Figma frame instead of turning them into editable settings.
- Building a new section for something that is 90% identical to an existing one, instead of adding a schema setting to the existing section.
- Ignoring how the layout behaves when a merchant adds a 6th block to a design that assumed exactly 3.
- Translating Figma's fixed pixel grid literally instead of building a fluid, responsive layout with the same visual proportions.
Examples
{% schema %}
{
"name": "Feature grid",
"settings": [{ "type": "range", "id": "columns_desktop", "min": 2, "max": 4, "default": 3, "label": "Columns" }],
"blocks": [{ "type": "feature", "name": "Feature", "settings": [ ... ] }],
"max_blocks": 8
}
{% endschema %}<div class="feature-grid">
<div class="feature">Fast Shipping</div> {/} literally copied from Figma, not editable
<div class="feature">Easy Returns</div>
<div class="feature">24/7 Support</div>
</div>Team Rules
- No section is built directly from a raw Figma link without a written Component Analysis first.
- Every text/image/color visible in the Figma frame becomes a schema setting unless explicitly marked "fixed brand element" by design.
- Missing responsive states are requested from design before implementation begins — engineers do not invent breakpoint behavior.
- Sections are tested against empty, minimum, and maximum content states before the PR is opened.
AI Instructions
- When given a Figma link or screenshot, first produce a written component/content breakdown before generating Liquid.
- Default to converting visible text and images into schema settings rather than hard-coding them, unless told otherwise.
- Ask about responsive behavior if only one breakpoint is visible in the provided design reference.
- Flag when a requested design duplicates an existing section's functionality instead of building a near-duplicate.
Validation Checklist
AI Development Strategy
Where AI accelerates us, where humans must own the decision, and what AI must never assume.
Overview
AI tools are extremely effective at converting a well-specified requirement into first-draft code. They are unreliable at inventing scope, judging UX trade-offs, or knowing what "correct" means when the spec is silent. This section draws that line explicitly so every tool — and every developer prompting one — knows which side of it they are on.
Why It Matters
The single biggest risk in an AI-assisted codebase is not bad code — lint catches that. It is confidently-generated code that silently answers a question nobody asked, based on a plausible-sounding assumption. That failure mode is invisible to Theme Check and invisible to a skimming reviewer, which is why it needs its own explicit strategy rather than being left to "use good judgment."
| Area | AI role | Human role |
|---|---|---|
| First-draft implementation | Generate code from a scoped, written requirement | Define the requirement; review the output |
| Refactoring / boilerplate | Do the mechanical transformation | Confirm behavior is unchanged |
| Architecture decisions | Propose options with trade-offs | Make the final call |
| UX / merchant experience judgment | Flag concerns based on this portal's rules | Own the decision |
| Accessibility semantics | Apply documented patterns correctly | Spot-check with real assistive tech |
| Scope | Never expand it | Define and approve any expansion |
Best Practices
- Give AI tools a written requirement and acceptance criteria, not a vague goal — specificity is what prevents assumption-filling.
- Ask AI to state its assumptions explicitly before generating code for anything underspecified, rather than silently picking one.
- Use AI for the first 80% (structure, boilerplate, standard patterns) and reserve human time for the last 20% (judgment calls, edge cases, taste).
- Review AI output the same way you would review a junior engineer's PR — assume competence, verify everything.
Common Mistakes
- Treating AI output as finished work because it compiles and looks plausible.
- Letting an AI tool infer business requirements ("I assumed you wanted a sale badge here") without confirming.
- Re-prompting the same AI tool repeatedly to "just fix it" instead of stepping back to fix the underlying prompt or requirement.
- Using AI to write acceptance criteria for its own output — acceptance criteria must come from a human, before generation.
Examples
Team Rules
- AI never merges its own code — a human is accountable for every merge, without exception.
- Any AI-generated assumption that materially affects merchant-facing behavior must be called out in the PR for explicit human sign-off.
- Acceptance criteria are written by a human before AI generation begins, not reverse-engineered from AI output afterward.
- When AI and a human reviewer disagree on an implementation choice, the human's judgment prevails unless the portal explicitly rules otherwise.
AI Instructions
- Never assume unstated business logic (pricing rules, badge conditions, inventory thresholds) — ask, or implement the documented default and flag it clearly.
- State your assumptions and open questions at the top of your response before presenting code.
- Do not expand scope beyond what was requested, even if you can see an "obvious" related improvement — suggest it separately instead.
- When multiple valid architectural approaches exist, present the trade-offs and let the human choose rather than silently picking one.
Validation Checklist
Cursor Rules
How the shared handbook is mirrored into .cursor/rules for Cursor users.
Overview
Cursor reads project rules from .cursor/rules/*.mdc files. We generate one scoped rules file per major domain (Liquid, CSS, JS, testing) from the canonical handbook, each with metadata controlling when Cursor applies it (always, on specific globs, or on request).
Why It Matters
Cursor's glob-scoped rules mean an over-broad or missing scope silently produces wrong suggestions in files the rule was never meant to touch. Getting the scoping right, generated automatically rather than hand-maintained, is what keeps Cursor's autocomplete and chat aligned with the rest of the toolchain.
Best Practices
- Split rules by file-type glob (
sections/**/*.liquid,assets/**/*.css) so Cursor only applies relevant guidance in relevant files. - Keep each
.mdcfile short and link back to the full handbook section rather than duplicating prose. - Set
alwaysApply: trueonly for genuinely universal rules (naming, no hard-coded strings) — over-broad "always" rules dilute Cursor's context budget. - Regenerate
.cursor/rules/as part of the handbook build script, never edit by hand.
Common Mistakes
- One giant rules file with no glob scoping, so every suggestion in every file carries irrelevant context and dilutes the signal.
- Rules that restate entire style guides inline instead of linking to the canonical handbook section.
- Forgetting to regenerate
.cursor/rules/after a handbook change, leaving Cursor suggesting outdated patterns. - Using vague, unfalsifiable rule text ("write clean code") instead of specific, checkable statements.
Examples
---
description: Liquid section & schema standards
globs: ["sections/**/*.liquid", "blocks/**/*.liquid"]
alwaysApply: false
---
# Liquid Sections
- Every section ships a complete {% schema %} with at least one preset.
- No hard-coded merchant copy; route strings through locales/.
- See /docs/engineering-handbook.md#liquid-standards for the full standard.# rules.txt
Write good liquid code. Follow best practices. Be consistent with the rest of the theme.
{/} no globs, no scoping, no link to canonical source — Cursor has nothing concrete to applyTeam Rules
- Rules are split by domain and scoped with globs — no single catch-all rules file.
.cursor/rules/is regenerated automatically; manual edits are reverted in review.- Every rule file links back to its corresponding section of this portal for full context.
- New Cursor rule domains (e.g. a new file type) are proposed via the handbook, not added directly to
.cursor/rules.
AI Instructions
- When operating as Cursor, treat
.cursor/rulesas binding for files matching its globs, and defer to the linked handbook section for anything not covered. - If a task touches files outside any existing rule's glob, apply the general standards in Coding Standards and flag the gap.
- Do not suggest disabling or bypassing a scoped rule to "get something working faster."
Validation Checklist
Claude Code Rules
How the shared handbook is mirrored into CLAUDE.md for Claude Code sessions.
Overview
Claude Code reads project context from CLAUDE.md at the repo root. Ours is generated from the canonical handbook and kept intentionally short: a project summary, links to each handbook section by anchor, and the handful of rules that most affect agentic multi-file edits (folder structure, no unrequested scope changes, always run Theme Check before declaring a task done).
Why It Matters
Claude Code frequently operates across many files in one agentic session, so a rules file that is too long burns context budget on prose instead of task-relevant instruction, and a rules file that is too vague lets a long autonomous session drift from the standard over many edits. Precision and brevity here directly affect output quality.
Best Practices
- Keep
CLAUDE.mdunder roughly one page — link to handbook sections for depth instead of inlining everything. - List explicit "definition of done" commands (lint, Theme Check, tests) so an agentic session self-verifies before reporting completion.
- Call out the no-scope-creep rule explicitly — it is the single highest-value line for agentic multi-file sessions.
- Regenerate from the handbook on every relevant handbook change, same as every other tool's rules file.
Common Mistakes
- Writing a long, narrative
CLAUDE.mdthat reads like a mission statement instead of actionable, checkable rules. - Omitting the exact lint/Theme Check commands, forcing Claude Code to guess which script to run.
- Letting
CLAUDE.mdgo stale relative to the handbook after a folder-structure or naming-convention change. - Duplicating the entire coding-standards document inline, burning context on prose the agent doesn't need for most tasks.
Examples
# CLAUDE.md
## Project
Shopify Theme Store submission. OS 2.0 architecture. See /docs/engineering-handbook.md.
## Non-negotiables
- Every section ships a complete {% schema %} with a preset.
- No hard-coded merchant copy — use locales/.
- Run `shopify theme check` and `npm run lint` before declaring work done.
- Never expand scope beyond the request; ask instead.
## Reference
Full standards: /docs/engineering-handbook.md#coding-standards# CLAUDE.md
You are an expert Shopify developer with 20 years of experience. Always write perfect code.
{/} no concrete rules, no links, nothing Claude Code can actually check itself againstTeam Rules
CLAUDE.mdis generated from the handbook, never hand-maintained independently.- It always includes the exact CLI commands used to lint and Theme Check the project.
- It explicitly states the no-unrequested-scope-expansion rule in its own line, not buried in prose.
- Any agentic session's output is still subject to full Human Review —
CLAUDE.mdcompliance is necessary, not sufficient.
AI Instructions
- Before reporting a task complete, run the lint and Theme Check commands listed in CLAUDE.md and report their results.
- Do not modify files outside the scope of the current request; if a fix requires touching unrelated files, ask first.
- When CLAUDE.md links to a handbook section, treat that section as the authoritative detail — do not improvise beyond it.
- Summarize what was changed and why at the end of a session, referencing the specific requirement it satisfies.
Validation Checklist
Copilot Rules
How the shared handbook is mirrored into .github/copilot-instructions.md.
Overview
GitHub Copilot reads repository custom instructions from .github/copilot-instructions.md, applied to both inline completions and Copilot Chat/Workspace. Because Copilot leans more on inline, low-context suggestions than the agentic tools above, its instructions emphasize terse, always-applicable rules over long-form reasoning.
Why It Matters
Inline completion has the smallest context window of any tool in our stack — it often sees only the current file. The instructions file is the only lever we have to keep single-line and single-function suggestions aligned with theme-wide conventions like naming and locale usage.
Best Practices
- Lead with the rules most likely to matter for single-line/single-function completions: naming, locale usage, no framework imports.
- Use short bullet statements, not paragraphs — Copilot's instruction parsing rewards terse, unambiguous rules.
- Include a couple of concrete good/bad snippets inline since Copilot Chat benefits from pattern examples more than links.
- Review Copilot suggestions in PRs slightly more skeptically than agentic-tool output, since it has the least project context.
Common Mistakes
- Writing generic "be a great engineer" instructions that provide zero project-specific signal.
- Assuming Copilot will follow a link to the full handbook the way an agentic tool might — inline the essentials instead.
- Letting inline completions introduce framework imports (React hooks, etc.) that violate the Technology Stack rules.
- Not updating instructions after a naming-convention change, causing autocomplete to keep suggesting the old pattern.
Examples
# .github/copilot-instructions.md
- Liquid: snake_case for variables, kebab-case for CSS classes and file names.
- Never hard-code visible strings — use {{ 'key' | t }} with a matching locales/en.default.json entry.
- CSS: BEM-style class names scoped per component, custom properties for design tokens.
- JS: vanilla ES modules only, no framework imports.
- Full standards: /docs/engineering-handbook.mdPlease write high quality, production-ready code that follows industry best practices and modern conventions.
{/} generic boilerplate that could apply to any repo — gives Copilot no theme-specific signal at allTeam Rules
- .github/copilot-instructions.md is generated from the handbook and kept under ~40 lines, optimized for terseness.
- It contains at least one inline good/bad code example per major standard (naming, locales, no frameworks).
- Copilot-authored inline suggestions are reviewed with the same rigor as any other AI-authored code — no exceptions for "just autocomplete."
- Instructions are regenerated whenever naming conventions or the technology stack change.
AI Instructions
- When completing Liquid, CSS, or JS in this repo, apply the naming and locale rules from copilot-instructions.md even for single-line suggestions.
- Never suggest importing a UI framework or a new npm dependency inline — flag it as a chat suggestion instead, for human review.
- Prefer matching the exact patterns already present in the surrounding file over introducing a stylistically different but "valid" alternative.
Validation Checklist
Universal Prompt Templates
Tool-agnostic prompt structures that work identically in Cursor, Claude Code, Copilot Chat, or ChatGPT.
Overview
A good prompt for this codebase is tool-agnostic by design: it states the requirement, the acceptance criteria, the relevant handbook sections, and explicit constraints. Any of our AI tools, given the same structured prompt, should produce functionally equivalent output.
Why It Matters
If prompt quality is inconsistent across developers, output quality will be inconsistent regardless of how good the handbook is — a perfect rules file cannot compensate for a one-line vague prompt. Templates remove prompt-writing skill as a variable.
Best Practices
- Structure every non-trivial prompt as: TASK, CONTEXT, REQUIREMENTS, CONSTRAINTS, ACCEPTANCE CRITERIA — in that order, every time.
- Explicitly reference the relevant portal section numbers so the AI grounds its output in a checkable standard, not general training-data conventions.
- End every prompt with permission (and expectation) to ask a clarifying question rather than guess.
- Reuse a saved template per task type (new section, bug fix, refactor, test) rather than freehand prompting each time.
Common Mistakes
- One-line prompts ("build X like Y") that force the AI to fill every gap with an assumption.
- Prompts that describe the desired look but never state constraints (no new deps, must be accessible, must be schema-driven).
- Forgetting acceptance criteria, so there is no way to verify the AI's output actually satisfies the task afterward.
- Copy-pasting a Figma screenshot with no text requirements, relying entirely on the AI's visual interpretation.
Examples
TASK: Build a "Testimonial carousel" section.
CONTEXT: New section for the homepage, per Figma frame [link]. Component breakdown attached.
REQUIREMENTS:
- Merchant can add up to 8 testimonial blocks (quote, author, rating).
- Follows Section Development Guidelines (portal §22) and Component Architecture (§21).
- All copy via locales/, no hard-coded strings.
- Must degrade gracefully to a static list with JS disabled (Progressive Enhancement, §8 philosophy).
CONSTRAINTS:
- No new npm dependencies (Technology Stack §5).
- Must pass Theme Check + axe-core with zero errors.
ACCEPTANCE CRITERIA:
- [ ] Schema includes preset, max_blocks: 8
- [ ] Keyboard-navigable carousel controls
- [ ] Lighthouse Accessibility unaffected (≥90)
If anything above is ambiguous, ask before generating code.Make a testimonials section like the one on the Figma file, make it look good and modern.
{/} no requirements, no constraints, no acceptance criteria, no explicit permission to ask questionsTeam Rules
- Non-trivial prompts follow the TASK / CONTEXT / REQUIREMENTS / CONSTRAINTS / ACCEPTANCE CRITERIA structure.
- Prompts reference specific portal section numbers rather than restating standards from memory.
- Every prompt template explicitly invites the AI to ask clarifying questions instead of guessing.
- Saved prompt templates live in
/docs/prompts/and are updated as the handbook evolves.
AI Instructions
- When you receive a structured prompt, verify each acceptance criterion against your output before presenting it as complete.
- If a prompt omits requirements, constraints, or acceptance criteria that this template structure implies should exist, ask for them.
- Do not proceed past an ambiguous requirement by picking the "most likely" interpretation without flagging it.
Validation Checklist
Coding Standards
The cross-language principles that every file in this repo follows, regardless of extension.
Overview
Before the language-specific standards (Liquid, HTML, CSS, JS, JSON) there are principles that apply everywhere: consistent formatting (enforced by Prettier, not debated), self-documenting names over comments, small single-purpose files, and no dead code left "just in case."
Why It Matters
Language-specific rules only work if the cross-cutting principles underneath them are consistent — otherwise every language ends up with its own definition of "clean," and the codebase feels like it was written by different teams even when every individual file passes its own linter.
Best Practices
- Let Prettier own formatting entirely — never hand-format or debate spacing/quote-style in review, just run the formatter.
- Name things for what they are, not how they were built —
product-card.liquid, notcard-v2-final.liquid. - One file, one responsibility — if a section file is doing three unrelated jobs, split it.
- Delete dead code immediately; git history is the "just in case" backup, not commented-out blocks in the working tree.
Common Mistakes
- Leaving commented-out old implementations in place "in case we need to revert."
- Naming files/variables after implementation details or ticket numbers instead of their purpose (
fix-jira-4521.js). - Manually adjusting Prettier's output because "I prefer it this way" — this reintroduces the exact inconsistency the tool exists to eliminate.
- Letting a single section file balloon into handling layout, cart logic, and analytics tracking all at once.
Examples
// price.js — one job: format and render a price
export function formatPrice(cents, currency) {
return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(cents / 100);
}// utils.js — everything and nothing
function doStuff(x, type) {
if (type === 'price') { /* ... */ }
if (type === 'cart') { /* ... */ }
if (type === 'analytics') { /* ... */ }
// 400 more lines
}Team Rules
- Prettier runs on every commit via a pre-commit hook — formatting is never a manual PR review comment.
- File and variable names describe purpose, never implementation history or ticket IDs.
- No commented-out code is merged to main; if it needs to come back, git history has it.
- Any file exceeding roughly 300 lines is a signal to split responsibilities, not a target to defend.
AI Instructions
- Generate code already formatted to Prettier's config — do not hand-roll spacing or quote style.
- Never leave commented-out alternative implementations in generated output; pick one and remove the rest.
- Name every new file and export for its purpose, never for the prompt or ticket that requested it.
- If a generated file is growing beyond a single clear responsibility, propose splitting it rather than continuing to append.
Validation Checklist
Liquid Standards
How we write Shopify Liquid: whitespace, objects, schema, and translation.
Overview
Liquid is the templating layer merchants and Shopify's runtime both depend on. Our standards prioritize predictable whitespace control, defensive checks against missing data (a product with no image, a metafield that is nil), and strict separation of logic from presentation.
Why It Matters
Liquid errors fail silently in production far more often than JS or CSS errors — a bad {{ }} reference just renders blank instead of throwing in the browser console. Defensive, consistent Liquid is how we catch problems before a merchant's storefront quietly breaks for their customers.
Best Practices
- Always guard optional data:
{% if product.featured_image %}before referencingproduct.featured_image.alt. - Use whitespace control (
{%-/-%}) deliberately to avoid stray blank lines in rendered HTML. - Keep logic in snippets/sections thin — complex conditionals belong in well-named snippets, not sixty-line inline
{% if %}chains. - Every user-facing string goes through
| twith a real locale key, even for "obviously English-only" copy.
Common Mistakes
- Referencing nested object properties without checking the parent exists first, causing silent blank renders.
- Inconsistent whitespace control producing a soup of blank lines in rendered HTML that make browser dev tools harder to read.
- Writing business logic (discount math, inventory thresholds) directly in a section template instead of a well-tested snippet.
- Hard-coding "Add to cart" or similar strings instead of routing through locales.
Examples
{%- if product.featured_media -%}
{{ product.featured_media | image_url: width: 800 | image_tag: loading: 'lazy', alt: product.featured_media.alt | default: product.title }}
{%- else -%}
{{ 'product-1' | placeholder_svg_tag: 'placeholder-svg' }}
{%- endif -%}{{ product.featured_media | image_url: width: 800 | image_tag }}
{/} no guard for missing media, no lazy loading, no alt fallback, no placeholderTeam Rules
- Every reference to optional/nullable Liquid data is guarded with an
{% if %}check. - Whitespace control is applied consistently within a file — mixing controlled and uncontrolled tags in the same block is a review blocker.
- No inline business logic beyond simple presentation conditionals; extract to a snippet once a condition chain exceeds ~3 branches.
- All merchant- or customer-facing strings use
| tagainst a real locale key.
AI Instructions
- Always guard against nil/missing Shopify objects before dereferencing nested properties.
- Apply whitespace control consistently across generated Liquid, matching the surrounding file's existing style.
- Route all generated user-facing text through locale keys, and create the corresponding locales/en.default.json entry.
- Extract non-trivial conditional logic into a snippet rather than inlining it in a section.
Validation Checklist
HTML Standards
Semantic, valid, accessible markup as the non-negotiable foundation.
Overview
HTML in this theme is semantic first: <button> for actions, <a> for navigation, real heading hierarchy, real landmarks (<header>, <main>, <nav>, <footer>). CSS and JS are layered on top of markup that already makes sense with both switched off.
Why It Matters
Semantic HTML is the cheapest accessibility win available — a screen reader, a search crawler, and a keyboard user all get correct behavior for free from a <button> that a <div onclick> will never give them no matter how much ARIA is bolted on afterward.
Best Practices
- Use the native element for the job (
button,a,details,dialog) before reaching for ARIA roles on a genericdiv. - Maintain one logical
<h1>per page and a heading hierarchy with no skipped levels. - Wrap every major page region in a real landmark element, not a class-named
div. - Validate generated markup against the HTML spec (W3C validator / html-validate CI step) as part of every PR.
Common Mistakes
- Using
<div class="button">with a click handler instead of a real<button>, losing keyboard operability and focus styling for free. - Skipping heading levels for visual sizing reasons (
h2straight toh4because it "looked right"). - Nesting interactive elements (a button inside a link, a link inside a button) which is invalid HTML and breaks assistive tech.
- Div soup — wrapping every element in extra non-semantic containers instead of using CSS on the semantic element directly.
Examples
<button type="button" class="quick-add" aria-label="Add {{ product.title }} to cart">
{{ 'products.product.add_to_cart' | t }}
</button><div class="quick-add" onclick="addToCart()">
Add to cart
</div>
{/} not focusable, not keyboard-operable, no semantic role, screen readers announce nothing usefulTeam Rules
- Interactive elements are always real interactive HTML elements, never a styled
divwith a click handler. - Heading hierarchy has exactly one
h1per rendered page and no skipped levels. - Every section's root markup sits inside or contributes to a real landmark region.
- Generated markup passes HTML validation with zero errors before merge.
AI Instructions
- Default to the correct native HTML element before adding ARIA roles to a generic container.
- Never attach click handlers to non-interactive elements without also making them properly keyboard-operable, or use a native element instead.
- Maintain correct heading hierarchy across the section being generated relative to the page it will be placed on.
- Validate that generated markup has no nested-interactive-element violations.
Validation Checklist
CSS Standards
Plain CSS, custom properties as design tokens, scoped and predictable specificity.
Overview
Theme Store rules require plain CSS — no committed SCSS/LESS. We use CSS custom properties as our design-token layer (colors, spacing, typography scale), BEM-inspired class naming scoped per component, and low, predictable specificity throughout.
Why It Matters
Predictable specificity is what makes CSS maintainable by many hands (and many AI tools) over time. A codebase where any class might be overridden by an !important three files away is a codebase no one — human or AI — can safely edit with confidence.
Best Practices
- Define all colors, spacing, and type scale as CSS custom properties on
:root, sourced from theme settings — never hard-code a hex value in a component file. - Scope component styles with a component-prefixed class (
.product-card__title) instead of relying on element selectors or deep nesting. - Keep specificity flat: single class selectors, no ID selectors,
!importantbanned outside of narrowly justified utility overrides. - Co-locate a section's CSS in its own scoped asset file, loaded only where that section is used.
Common Mistakes
- Hard-coding a hex color or pixel value that duplicates an existing design token instead of referencing the custom property.
- Deeply nested selectors (
.section .row .col .card .title) that create fragile, hard-to-override specificity. - Reaching for
!importantto win a specificity fight instead of fixing the underlying selector structure. - Global, unscoped class names (
.title,.button) that collide across unrelated components.
Examples
.product-card {
--card-gap: var(--spacing-4);
display: grid;
gap: var(--card-gap);
}
.product-card__title {
color: var(--color-text);
font-size: var(--font-size-md);
}.card .row .col .title {
color: #1a1f2e !important;
font-size: 18px;
}
{/} hard-coded color, magic pixel value, deep nesting, !importantTeam Rules
- No committed SCSS/LESS — plain CSS only, per Theme Store requirements.
- Colors, spacing, and typography reference custom properties, never hard-coded values.
!importantrequires an inline comment justifying why, and tech-lead sign-off in review.- Class names are scoped to their component (BEM-style) — no bare, global class names.
AI Instructions
- Reference existing CSS custom properties for color/spacing/type instead of inventing new hard-coded values.
- Use flat, component-scoped class selectors — avoid nesting more than two levels or using ID selectors.
- Never use !important to resolve a specificity conflict; fix the selector instead, or flag the conflict for human review.
- Place generated styles in the correct section-scoped asset file per Folder Structure, not in a shared global stylesheet unless truly global.
Validation Checklist
JavaScript Standards
Vanilla, progressively-enhanced, defensively-written ES modules.
Overview
JavaScript enhances server-rendered Liquid — it never gates core functionality. We write native ES modules, scoped with Web Components where state encapsulation helps, and always assume the script might load late, fail to load, or run against DOM the merchant has customized in ways we did not anticipate.
Why It Matters
Shopify storefronts run on real-world networks and real-world devices, including flaky mobile connections. Code that assumes JS always loads instantly and DOM always matches its expected shape breaks for exactly the merchants and customers who can least afford a broken storefront.
Best Practices
- Feature-detect and null-check DOM queries before using them — never assume an element exists just because it usually does.
- Use
deferortype="module"loading and ensure the underlying HTML/CSS functions before JS has run. - Encapsulate stateful interactive components as native Web Components (
customElements.define) instead of a soup of global event listeners. - Debounce/throttle expensive handlers (scroll, resize, input) and clean up listeners when elements are removed.
Common Mistakes
- Querying the DOM without a null check, throwing a runtime error the moment a merchant removes the block that element depended on.
- Relying on JS to render core content (e.g. product price) instead of having it present in server-rendered HTML and merely enhanced by JS.
- Attaching event listeners globally with no cleanup, leaking memory on themes with client-side section re-renders (Theme Editor live preview).
- Writing one monolithic
theme.jsloaded on every page instead of scoping scripts per section/template.
Examples
class QuantitySelector extends HTMLElement {
connectedCallback() {
this.input = this.querySelector('input');
if (!this.input) return;
this.addEventListener('click', this.handleClick.bind(this));
}
handleClick(e) {
const delta = e.target.dataset.step;
if (!delta) return;
this.input.value = Math.max(1, Number(this.input.value) + Number(delta));
this.input.dispatchEvent(new Event('change', { bubbles: true }));
}
}
customElements.define('quantity-selector', QuantitySelector);document.querySelector('.qty-input').addEventListener('click', function(e) {
// throws if .qty-input isn't on the page; global listener never cleaned up
document.querySelector('.qty-input').value++;
});Team Rules
- All DOM queries are null-checked before use.
- Core content and functionality work with JS disabled; JS only enhances (progressive enhancement, non-negotiable).
- Stateful interactive components are Web Components, not ad hoc global listeners.
- Scripts are scoped per section/template via the asset pipeline, not bundled into one global file loaded everywhere.
AI Instructions
- Always null-check DOM queries before using the result; never assume an element is present.
- Ensure any generated feature has a working non-JS fallback before adding JS enhancement on top.
- Prefer a Web Component for anything with internal state over global query-and-listen patterns.
- Scope generated JS assets to the section/template that needs them, per Folder Structure conventions.
Validation Checklist
JSON Standards
Schema files, templates, and locale files — structured, validated, and diff-friendly.
Overview
JSON drives templates, section schemas, theme settings, and locale strings. We keep it strictly valid (no trailing commas, no comments — Liquid schema JSON does not support them), consistently key-ordered, and formatted for minimal, reviewable diffs.
Why It Matters
A single malformed schema JSON block breaks the entire section in the Theme Editor with a cryptic error, and Shopify does not provide rich in-editor JSON debugging. Strict, validated JSON is what prevents an AI-introduced trailing comma from silently taking down a merchant's homepage editor.
Best Practices
- Validate every
{% schema %}block and everytemplates/*.jsonfile against Shopify's JSON schema in CI before merge. - Keep key ordering consistent within schema objects (
name,settings,blocks,presets) across every section for scanability. - Use descriptive, stable
idvalues for settings — renaming an id after merchants have saved data orphans their configuration. - Format JSON with Prettier so diffs show only the actual change, not incidental reformatting.
Common Mistakes
- A trailing comma or unescaped quote in a schema block that silently breaks the whole section in the Theme Editor.
- Renaming a setting's
idafter launch, silently discarding every merchant's saved value for that setting. - Inconsistent key ordering across section files, making schemas harder to scan and diffs noisier than necessary.
- Adding comments inside schema JSON (unsupported) copied from an AI suggestion that didn't account for Liquid's JSON parser.
Examples
{
"name": "Rich text",
"settings": [
{ "type": "richtext", "id": "content", "label": "Content" }
],
"presets": [{ "name": "Rich text" }]
}{
"name": "Rich text",
"settings": [
{ "type": "richtext", "id": "content", "label": "Content", }, // trailing comma + comment: invalid JSON, breaks the section
],
}Team Rules
- Every schema JSON block is validated in CI; a validation failure blocks merge.
- Setting
idvalues are treated as a stable public contract — never renamed after a section has shipped. - JSON formatting is owned by Prettier; no manual reformatting in review.
- Locale JSON keys follow the same nesting/naming convention across every locale file.
AI Instructions
- Always produce strictly valid JSON — no trailing commas, no comments, correctly escaped strings.
- Never rename an existing setting id when modifying a section's schema; add a new setting instead if behavior must change.
- Match existing key ordering conventions (name, settings, blocks, presets) when generating new schema blocks.
- Add corresponding locale entries for any new label/string introduced in a schema.
Validation Checklist
Naming Conventions
One naming system across files, CSS classes, JS, Liquid variables, and schema ids.
Overview
Naming is the single highest-leverage consistency rule in a multi-tool codebase, because every AI tool has its own naming instincts by default. We fix that variance with one explicit convention per context, documented once, applied everywhere.
Why It Matters
Inconsistent naming is the fastest way a reviewer (or a future engineer) can tell a codebase had multiple uncoordinated authors — even when the underlying logic is fine. It is also the cheapest thing to get consistently right, which makes it the highest ROI rule in this whole portal.
| Context | Convention | Example |
|---|---|---|
| Files (Liquid, JS, CSS) | kebab-case | product-card.liquid, cart-drawer.js |
| CSS classes | BEM-ish, component-scoped kebab-case | .product-card__title--sale |
| Liquid / JS variables | snake_case (Liquid), camelCase (JS) | featured_product (Liquid), featuredProduct (JS) |
| Schema setting ids | snake_case, stable and descriptive | show_vendor, heading_size |
| Section/block type ids | kebab-case, purpose-first | featured-collection, testimonial |
| Locale keys | dot-namespaced by feature | products.product.add_to_cart |
| Web Component tags | kebab-case, always hyphenated | quantity-selector, cart-drawer |
Best Practices
- Name for purpose, not position or implementation ("testimonial-carousel", not "section-7" or "carousel-v2").
- Keep one convention per context and never mix (no camelCase CSS classes, no kebab-case JS variables).
- Make schema setting ids self-explanatory without reading the label —
show_vendor, notopt1. - Prefix flat
assets/file names with their owning component to avoid collisions (see Folder Structure).
Common Mistakes
- Version-suffixing names (
hero-v2.liquid,card-final-final.js) instead of replacing the original. - Mixing naming cases within the same context because different AI tools defaulted differently on different days.
- Cryptic abbreviations that made sense to one author in the moment (
tst_crslfor "testimonial carousel"). - Naming a setting after its current UI position ("second_button") which becomes wrong the moment merchants reorder blocks.
Examples
{/} sections/testimonial-carousel.liquid
{% schema %}
{ "settings": [{ "id": "show_ratings", "type": "checkbox", "label": "Show star ratings" }] }
{% endschema %}{/} sections/section-7-FINAL-v2.liquid
{% schema %}
{ "settings": [{ "id": "opt2", "type": "checkbox", "label": "Show star ratings" }] }
{% endschema %}Team Rules
- This table is the single naming reference — any naming question is answered by checking it, not by individual preference.
- No version suffixes (v2, final, new, old) in any file, class, or variable name — git history is the version history.
- Setting ids describe what the setting controls, never its current position in the UI.
- Naming convention violations are a review blocker, treated the same as a failing lint rule.
AI Instructions
- Apply the exact convention per context from this table — do not default to your own training-data naming habits.
- Never generate version-suffixed names; if replacing something, replace it, don't create a parallel "v2" file.
- Name schema settings for what they control, not their position or the order they were requested in.
- When in doubt about a name, prefer the more descriptive, purpose-first option over the shorter one.
Validation Checklist
Component Architecture
How sections, blocks, and snippets compose into a coherent system rather than a pile of one-offs.
Overview
Three composition primitives cover everything in this theme: sections (page-level, schema-driven, one per distinct page region), theme blocks (nestable, reusable content units a merchant arranges within a section), and snippets (developer-facing partials with no schema, reused across many sections for presentation logic).
Why It Matters
Without a clear composition model, every new feature request tempts an AI tool to generate a new one-off section that duplicates 80% of an existing one. A clear "which primitive does this belong to" decision tree keeps the component count proportional to actual design variety, not to the number of prompts written.
| Primitive | Use when… | Has schema? |
|---|---|---|
| Section | It represents a distinct, addable page region (hero, featured collection, FAQ) | Yes — full schema + presets |
| Theme block | Merchants need to add/remove/reorder repeatable content within a section | Yes — block-level schema |
| Snippet | It's reusable presentation logic with no merchant-facing configuration (price display, icon, media) | No — plain Liquid partial |
Best Practices
- Before creating a new section, check whether the need is actually a new block type or setting on an existing section.
- Design blocks to be genuinely reusable across sections (a "testimonial" block usable in both a dedicated section and a grid section), not bound to one parent.
- Keep snippets pure where possible — pass in data, render markup, no side effects or global state mutation.
- Document each section's intended blocks and settings in a short comment at the top of the schema for future maintainers.
Common Mistakes
- Creating "hero-v2" as a whole new section instead of adding a layout variant setting to the existing hero section.
- Building a block that only works inside one specific section, defeating the purpose of the reusable block model.
- Putting merchant-configurable settings on a snippet — snippets have no schema and cannot be edited in the Theme Editor.
- Letting the section count grow unbounded because each new Figma frame becomes a new file instead of a variant.
Examples
{/} blocks/testimonial.liquid — reusable across any section that accepts @theme blocks
{% schema %}
{ "name": "Testimonial", "settings": [ { "type": "richtext", "id": "quote" }, { "type": "text", "id": "author" } ] }
{% endschema %}{/} sections/testimonial-grid-hero-variant.liquid — one-off, not reusable, duplicates 90% of an existing sectionTeam Rules
- Every new component request is evaluated against the decision table above before a new file is created.
- Blocks are designed for reuse across at least two plausible parent sections, not hard-bound to one.
- Snippets never carry a
{% schema %}block — if it needs merchant configuration, it is a section or block. - A running component inventory (sections, blocks, snippets) is kept current in
/docs/component-inventory.md.
AI Instructions
- Before generating a new section, check whether the requirement fits an existing section plus a new setting or block instead.
- Design new blocks to be reusable, not scoped to a single parent section, unless there is a stated reason they must be exclusive.
- Never add a schema block to a snippet — if configuration is needed, recommend converting it to a section or block.
- Flag when a request appears to duplicate existing component functionality rather than building the duplicate silently.
Validation Checklist
Section Development Guidelines
The checklist every new section satisfies before it is considered complete.
Overview
A section is not "done" when it renders correctly with sample data. It is done when it survives empty state, maximum content, merchant misconfiguration, and every supported breakpoint — and when its schema gives a merchant enough control to actually use it without reading documentation.
Why It Matters
Sections are the unit merchants interact with directly in the Theme Editor. A section that only works with the "happy path" content the developer tested against is a support ticket waiting to happen the first time a real merchant's data doesn't match that assumption.
Best Practices
- Design schema settings in the order a merchant would think about them (content first, then style, then advanced/layout) not the order they were coded.
- Provide sensible
defaultvalues for every setting so a freshly-added section looks intentional before any merchant configuration. - Support
max_blocksand empty-block states explicitly — test with 0 blocks and with the maximum. - Every section works standalone on any template it is enabled for — no hidden dependency on another section existing above it.
Common Mistakes
- A section that looks broken or empty until a merchant discovers exactly the right combination of settings.
- No default values, so a freshly-added section renders empty/blank in the Theme Editor before configuration.
- Assuming a fixed number of blocks (exactly 3) instead of handling 0, 1, and the schema-defined maximum gracefully.
- A section silently depending on markup/CSS from a different section, breaking if that other section is removed or reordered.
Examples
{% schema %}
{
"name": "FAQ",
"settings": [{ "type": "text", "id": "heading", "default": "Frequently asked questions" }],
"blocks": [{ "type": "question", "name": "Question", "settings": [...] }],
"max_blocks": 20,
"presets": [{ "name": "FAQ", "blocks": [{ "type": "question" }, { "type": "question" }] }]
}
{% endschema %}{% schema %}
{ "name": "FAQ", "settings": [{ "type": "text", "id": "heading" }], "blocks": [{ "type": "question" }] }
{/} no default heading, no max_blocks, no preset with sample blocks — looks broken until configured just right
{% endschema %}Team Rules
- Every setting ships with a sensible
default— no section renders visibly broken on first add. - Every section is tested with 0 blocks, 1 block, and its schema-defined maximum before PR is opened.
- A section never assumes another specific section is present elsewhere on the page.
- Presets include realistic sample block configurations, not empty arrays.
AI Instructions
- Always include sensible default values for every schema setting you generate.
- Test (or explicitly note the need to test) the section at zero, one, and maximum block count before declaring it complete.
- Never write markup or logic that assumes a specific sibling section exists on the page.
- Generate presets with realistic sample block content, not empty placeholders.
Validation Checklist
Snippet Guidelines
Pure, parameterized, well-documented Liquid partials.
Overview
Snippets are the theme's function library. Each one takes explicit parameters via {% render %}, does one job, and has zero implicit dependency on variables leaking in from its caller's scope.
Why It Matters
{% render %} (unlike the deprecated {% include %}) is properly scoped — snippets cannot silently read variables from the calling template. Writing snippets as if they could is a common source of "works here, breaks there" bugs when a snippet is reused in a new context.
Best Practices
- Always call snippets with
{% render 'name', param: value %}, passing every value the snippet needs explicitly. - Document expected parameters in a comment at the top of the snippet file (name, type, required/optional).
- Provide a sensible fallback or early-return for missing/nil required parameters instead of failing silently mid-render.
- Keep snippets under roughly 60 lines — if it's doing more, it's probably more than one snippet.
Common Mistakes
- Using the deprecated
{% include %}, which leaks the caller's entire variable scope into the snippet. - Writing a snippet that assumes a variable like
productis always in scope instead of receiving it as an explicit parameter. - No parameter documentation, forcing every future caller (human or AI) to read the entire snippet body to figure out what to pass.
- A "kitchen sink" snippet handling five loosely related use cases via a pile of optional flags.
Examples
{/} snippets/price.liquid
{/} Params: product (required), show_compare_at (bool, optional, default false)
{%- liquid
assign show_compare = show_compare_at | default: false
-%}
<span class="price">{{ product.price | money }}</span>
{%- if show_compare and product.compare_at_price > product.price -%}
<s class="price--compare">{{ product.compare_at_price | money }}</s>
{%- endif -%}{/} snippets/price.liquid — relies on caller scope via deprecated include, no documented params
<span class="price">{{ product.price | money }}</span>Team Rules
{% include %}is banned; all partials use{% render %}exclusively.- Every snippet documents its parameters in a header comment.
- Snippets never assume implicit scope — every value used is either a passed parameter or a Shopify global (like
settings). - Snippets over ~60 lines are split unless a clear single responsibility justifies the length.
AI Instructions
- Always use {% render %} with explicit parameters, never {% include %}.
- Document parameters in a header comment for every snippet you generate.
- Do not assume any variable is in scope inside a snippet unless it was explicitly passed as a parameter.
- If a snippet is growing to handle multiple unrelated use cases, propose splitting it into separate snippets.
Validation Checklist
Accessibility
WCAG 2.1 AA as the floor, not the ceiling — built in, not bolted on.
Overview
Accessibility is verified the same way performance is: automated checks in CI (axe-core, Lighthouse Accessibility ≥ 90) plus manual verification with an actual keyboard and screen reader for every new interactive pattern. It is a stated project goal (§2), not a nice-to-have.
Why It Matters
Accessible markup is also more robust markup — the same semantic HTML that helps a screen reader user also helps search engines, helps keyboard power users, and tends to be simpler to maintain. Treating it as launch-blocking, not optional polish, is both an ethical obligation and a Theme Store submission requirement.
Best Practices
- Every interactive component is operable with keyboard alone: visible focus states, logical tab order, no keyboard traps.
- Color is never the only signal (sale badges, form errors, required fields all get a text or icon indicator too).
- Images have meaningful
alttext (or emptyalt=""for purely decorative images) — never omitted, never the filename. - Custom interactive widgets (carousels, dropdowns, modals) follow WAI-ARIA Authoring Practices patterns exactly, not an improvised approximation.
Common Mistakes
- Removing focus outlines with CSS for aesthetic reasons without providing an equally visible custom focus style.
- Building a custom dropdown/modal from scratch without following the established ARIA pattern, producing something that looks right but is unusable by screen reader users.
- Color contrast that passes a casual glance but fails WCAG AA's 4.5:1 ratio for body text.
- Decorative icons announced by screen readers because they lack
aria-hidden="true", creating noisy, confusing announcements.
Examples
<button type="button" aria-expanded="false" aria-controls="mobile-nav" class="menu-toggle">
<span class="visually-hidden">{{ 'accessibility.menu' | t }}</span>
{% render 'icon', name: 'menu' %}
</button><div class="menu-toggle" onclick="toggleMenu()">
{% render 'icon', name: 'menu' %}
</div>
{/} not a button, no aria-expanded state, no accessible label, unusable by keyboard or screen readerTeam Rules
- Lighthouse Accessibility ≥ 90 and zero critical axe-core violations are a merge gate, not a suggestion.
- Every custom interactive component is checked against the corresponding WAI-ARIA Authoring Practices pattern before merge.
- Focus states are never removed without a replacement that meets or exceeds default visibility.
- New interactive patterns get at least one manual pass with keyboard-only navigation before shipping.
AI Instructions
- Never remove or hide focus indicators without providing a visible, compliant replacement.
- When building a custom interactive widget, implement the matching WAI-ARIA Authoring Practices pattern exactly, including keyboard behavior.
- Provide meaningful alt text for informative images and empty alt for decorative ones — never omit the attribute.
- Flag any generated color combination that may fail WCAG AA contrast for human verification.
Validation Checklist
Performance
A performance budget enforced by CI, not a hope enforced by good intentions.
Overview
Performance is budgeted per page type (home, collection, product) and checked on every PR via Lighthouse CI against real Core Web Vitals thresholds — LCP, CLS, INP — not just an overall score. A change that regresses the budget fails the build before it can reach a human reviewer.
Why It Matters
Performance regressions are almost always incremental — one more script, one more web font, one more unoptimized image, each individually "fine." Without an automated budget, a codebase silently accumulates a slow storefront one reasonable-looking PR at a time. The budget is what makes each PR accountable for its own cost.
Best Practices
- Lazy-load below-the-fold images and iframes (
loading="lazy"), and never lazy-load the LCP image. - Load JS with
defer/module scoping so parsing never blocks first paint; avoid render-blocking synchronous scripts entirely. - Self-host or subset web fonts and set
font-display: swapto avoid invisible-text flashes. - Preconnect/preload only the handful of critical resources — over-preloading is itself a performance cost.
Common Mistakes
- Loading a full icon-font or full font-weight family when the design uses two weights and a handful of glyphs.
- Render-blocking third-party scripts (chat widgets, review widgets) loaded synchronously in
<head>. - Serving one large fixed-size image to every viewport instead of responsive
srcset/sizes. - Layout shift from images or ads without reserved space (missing
width/heightoraspect-ratio).
Examples
{{ product.featured_media | image_url: width: 1200 | image_tag:
loading: 'lazy',
sizes: '(min-width: 990px) 50vw, 100vw',
widths: '400,600,800,1200',
alt: product.featured_media.alt
}}<img src="{{ product.featured_media | image_url: width: 3000 }}" alt="">
{/} single oversized image for every viewport, no lazy loading, no srcset, empty alt loses accessibility tooTeam Rules
- Lighthouse CI runs on every PR against per-page-type budgets; a regression blocks merge.
- No render-blocking third-party script loads synchronously in <head> — defer, async, or load on interaction.
- All non-LCP images use responsive
srcset/sizesandloading="lazy". - Every image and embed reserves layout space to keep CLS at zero.
AI Instructions
- Always generate responsive image markup (srcset/sizes) rather than a single fixed-width image tag.
- Mark below-the-fold media as loading="lazy"; never lazy-load the page's likely LCP element.
- Load any third-party or non-critical script asynchronously or on interaction, never as a blocking <head> script.
- Reserve layout space (width/height or aspect-ratio) for every image and embed to prevent layout shift.
Validation Checklist
Responsive Images
Right-sized images for every viewport, using Shopify's built-in image pipeline.
Overview
Shopify's CDN can generate any image at any width on the fly via image_url. We always use it with a full srcset/sizes pair generated through image_tag, rather than shipping one oversized image to every device.
Why It Matters
Unoptimized images are consistently the single largest contributor to slow LCP on ecommerce sites — and Shopify already solves the hard part (on-the-fly resizing via CDN) for free. Not using it is leaving a solved problem unsolved.
Best Practices
- Always generate images through
image_tagwith explicitwidthsand asizesattribute matching actual rendered layout width per breakpoint. - Set explicit
width/heightor CSSaspect-ratioso the browser reserves space before the image loads. - Use WebP-capable delivery (Shopify's CDN handles format negotiation automatically via
image_url) rather than manually managing formats. - Reserve
loading="eager"andfetchpriority="high"only for the actual above-the-fold LCP candidate image.
Common Mistakes
- Hard-coding a single image width regardless of viewport, forcing mobile devices to download desktop-sized assets.
- Missing or inaccurate
sizesattribute, causing the browser to pick a larger image than the layout actually needs. - No
width/height, causing layout shift as images load in. - Lazy-loading the hero/LCP image, delaying the metric it is most likely to determine.
Examples
{{ image | image_url: width: 1600 | image_tag:
widths: '375,750,1100,1500',
sizes: '(min-width: 750px) 50vw, 100vw',
loading: 'eager',
fetchpriority: 'high',
alt: image.alt
}}<img src="{{ image | image_url: width: 1600 }}" alt="{{ image.alt }}">
{/} one fixed width shipped to every device, no responsive srcset, no fetchpriority signal for the LCP imageTeam Rules
- Every content image uses
image_tagwith a full responsivewidths/sizespair. sizesvalues are checked against actual rendered layout, not copy-pasted defaults.- Exactly one image per page is marked as the LCP candidate with eager loading and high fetch priority.
- Every image reserves explicit dimensions to prevent layout shift.
AI Instructions
- Generate all images via image_tag with widths and a sizes attribute derived from the actual layout, not a guess.
- Identify the likely LCP image for a given section and mark it eager/high-priority; lazy-load everything else below the fold.
- Always include explicit width/height or an aspect-ratio to prevent layout shift.
- Never emit a plain
for content images sourced from Shopify's CDN — always route through image_tag.
Validation Checklist
Theme Editor UX
Designing schema settings for the merchant actually using the Theme Editor, not for the developer who wrote them.
Overview
The Theme Editor is a UI a non-technical merchant uses live, with a real-time preview. Every schema setting we write is a piece of UI design in its own right: label wording, help text, input type choice, and ordering all directly affect whether a merchant can configure the theme without opening a support ticket.
Why It Matters
A technically correct schema that is confusing in the Theme Editor still fails the project — Merchant Experience First and Theme Editor Experience First are both explicit project principles (§1). An engineer who ships a working-but-confusing setting has not actually finished the task.
Best Practices
- Choose the narrowest correct input type —
rangefor bounded numeric choices,selectfor a fixed set of options, not a free-text field for either. - Write labels in merchant language ("Show product ratings"), not developer language ("enable_pdp_rating_flag").
- Add concise
infohelp text for any setting whose effect isn't obvious from the label alone. - Group related settings with
headerelements in the order a merchant would think through them: content, then style, then advanced.
Common Mistakes
- Free-text input for a value that should be a bounded range or fixed select, inviting invalid values.
- Developer-facing jargon in labels ("z-index override") that means nothing to a merchant.
- No help text on a setting whose effect is genuinely ambiguous from the label alone ("Enable mode B").
- Dozens of ungrouped settings in a flat list, forcing merchants to scroll and guess at organization.
Examples
{ "type": "header", "content": "Layout" },
{ "type": "range", "id": "columns_desktop", "min": 2, "max": 4, "step": 1, "default": 3, "label": "Columns on desktop" },
{ "type": "select", "id": "image_ratio", "options": [{"value":"square","label":"Square"},{"value":"portrait","label":"Portrait"}], "default": "square", "label": "Image ratio", "info": "Applies to all product images in this section." }{ "type": "text", "id": "cols", "label": "Cols (2-4)" }
{/} free text for a bounded number, cryptic label, no help text, no validation against the actual constraintTeam Rules
- Input type must be the narrowest type that correctly constrains the value (range/select over free text where a fixed set of valid values exists).
- All labels use plain merchant-facing language, reviewed by a non-engineer if genuinely ambiguous.
- Settings are grouped with headers in a content-first, then-style, then-advanced order.
- Any setting whose effect is not obvious from its label alone gets help text.
AI Instructions
- Choose schema input types based on the narrowest correct constraint, not the fastest type to write.
- Write setting labels in plain, merchant-facing language, avoiding internal/technical terminology.
- Add info help text whenever a setting's effect might not be obvious from its label alone.
- Group generated settings with header elements in a sensible, merchant-intuitive order.
Validation Checklist
Merchant Experience
The merchant is a user too — install, first configuration, and ongoing edits must all feel considered.
Overview
Beyond individual schema settings (§27), merchant experience covers the whole lifecycle: install and first preview, discoverability of features, safe defaults, and forgiving behavior when a merchant does something unexpected (removes every block, uploads a huge image, leaves a field blank).
Why It Matters
Merchants are the actual customers of a Theme Store product — the shopper never sees a setting the merchant never configures. A theme that is powerful for engineers but confusing for the merchant who paid for it fails its actual purpose, regardless of code quality underneath.
Best Practices
- A freshly-installed theme should look intentional and complete out of the box, before any merchant configuration.
- Never let a merchant action produce a broken-looking storefront — empty states render gracefully, not as blank space or errors.
- Provide onboarding-friendly presets (§22) so adding a new section starts from something usable, not a blank slate.
- Write documentation and in-editor help text assuming zero prior Shopify theme experience.
Common Mistakes
- A default install that looks like a placeholder/lorem-ipsum demo instead of a real, professional storefront.
- A section that renders as visibly broken markup the moment a merchant removes all its blocks.
- Features that exist but are undiscoverable because they are buried in a poorly labeled advanced settings group.
- Assuming the merchant configuring the theme has the same technical vocabulary as the engineer who built it.
Examples
Team Rules
- Default preset content must look production-ready, not placeholder, in the initial Theme Editor preview.
- Every section defines and tests its zero-content behavior explicitly (hide, or render a tasteful empty state).
- Feature discoverability is reviewed from a "first-time merchant" perspective before shipping, not just a developer's perspective.
- No merchant-facing copy assumes prior Shopify or web-development knowledge.
AI Instructions
- When generating preset content, make it look like a real, finished storefront rather than obvious placeholder text.
- Explicitly handle the zero-content/zero-block state for every section you generate.
- Write any merchant-facing copy (labels, help text, empty states) assuming no prior technical background.
- Flag settings or features that might be hard for a first-time merchant to discover or understand.
Validation Checklist
Theme Store Requirements
The non-negotiable submission bar — cross-referenced with our own Submission Requirements doc.
Overview
This project already maintains a dedicated, detailed Submission Requirements reference. This section is the engineering-facing summary: the requirements that must be designed in from day one because retrofitting them pre-submission is expensive (required page templates, required features, Lighthouse thresholds, browser support, code-quality rules).
Why It Matters
Theme Store rejection for a structural gap (a missing required template, a disallowed dependency, a failed Lighthouse threshold) discovered at submission time can mean weeks of rework. Treating these as day-one architectural constraints, not a pre-launch checklist, is dramatically cheaper.
Best Practices
- Build every required page template (§ full list in Submission Requirements) from the start, not as a pre-submission scramble.
- Keep Lighthouse Performance ≥ 60 and Accessibility ≥ 90 as a continuously-enforced CI gate, not a one-time pre-submission fix.
- Support the required feature set (subscriptions, gift cards, faceted search, etc.) architecturally from the first relevant section, not bolted on later.
- Avoid any restricted pattern (fake urgency, app-dependent features, unlicensed assets) at the design stage, before code is even written.
Common Mistakes
- Discovering a missing required template only during pre-submission QA, requiring a late scramble.
- Letting Lighthouse scores drift below threshold over months of feature work because it was only checked once at kickoff.
- Building a feature that silently depends on a third-party app being installed, which violates Theme Store independence rules.
- Using stock imagery or icon sets without confirming license terms are compatible with commercial redistribution.
Examples
Team Rules
- Every required Theme Store page template exists and is functional before a section of the theme is considered feature-complete.
- Lighthouse thresholds are a continuous CI gate, checked on every PR, not just before submission.
- No feature ships with a silent dependency on a third-party app being installed.
- Any third-party asset (image, icon, font) has a confirmed commercial-use license documented in the PR.
AI Instructions
- When building any page-level feature, check it against the required template/feature list rather than assuming it's optional.
- Never introduce a feature that silently requires a third-party app to function correctly.
- Flag any third-party asset usage for a license check rather than assuming it's free to use commercially.
- Treat Theme Store Lighthouse thresholds as a hard constraint on any change you propose, not an afterthought.
Validation Checklist
Theme Check Rules
Shopify's official static analyzer for Liquid — our first and non-negotiable line of defense.
Overview
Theme Check (Shopify's official Liquid linter, run via the Shopify CLI or VS Code/Cursor extension) catches Liquid syntax errors, deprecated tags, missing translations, performance anti-patterns, and accessibility issues specific to Liquid templates. It runs locally on save and again in CI on every PR.
Why It Matters
Theme Check encodes Shopify's own platform-specific knowledge that generic linters cannot — deprecated Liquid objects, Online Store 2.0 best practices, and Theme Store submission rules. Treating its output as advisory rather than blocking is how easily-preventable submission rejections happen.
Best Practices
- Extend
theme-check:recommendedas the baseline and add project-specific rule tightening on top, rather than starting from an empty config. - Run Theme Check as a pre-commit hook so violations are caught before a PR is even opened.
- Treat every Theme Check error as a merge blocker and every warning as requiring an explicit justification comment if not fixed.
- Keep the Theme Check CLI version pinned and updated deliberately, not silently auto-updated mid-project.
Common Mistakes
- Running Theme Check manually and inconsistently instead of wiring it into pre-commit hooks and CI.
- Suppressing a Theme Check warning with an inline ignore comment instead of fixing the underlying issue.
- Letting Theme Check warnings accumulate for months until the count is too large to meaningfully review.
- Using a stale or default config that misses project-specific checks like translation key existence.
Examples
{/} .theme-check.yml
extends: theme-check:recommended
ignore:
- node_modules
UnusedAssign:
enabled: true
MissingTemplate:
enabled: true
TranslationKeyExists:
enabled: true
RemoteAsset:
enabled: true{/} no .theme-check.yml — defaults may miss project-specific rules, and there's nothing enforcing it in CI
{/} developers run `shopify theme check` inconsistently, if at allTeam Rules
- Theme Check runs in CI on every PR; any error blocks merge.
- Warnings require either a fix or an inline-commented justification approved in review — silent suppression is not allowed.
- The Theme Check config is versioned in the repo and reviewed the same as any other tooling config change.
shopify theme checkis included in every AI tool's "definition of done" (see §11 CLAUDE.md example).
AI Instructions
- Run (or instruct the developer to run) shopify theme check before declaring any Liquid change complete.
- Never suppress a Theme Check warning with an ignore comment without an explicit, stated justification.
- Treat every Theme Check error as something to fix immediately, not defer.
- When generating Liquid, proactively avoid patterns Theme Check flags (deprecated objects, missing alt text, unused assigns).
Validation Checklist
ESLint Rules
Catching JavaScript bugs and style drift before they reach review.
Overview
ESLint runs against every .js file in assets/, extending a strict recommended config plus rules specific to our progressive-enhancement, no-framework approach (no unused variables, no implicit globals, mandatory null checks flagged via eslint-plugin-unicorn's safety rules).
Why It Matters
Vanilla JS without a framework's guardrails needs a linter to catch the mistakes a framework would otherwise prevent structurally — global scope leaks, missing null checks, forgotten event listener cleanup. ESLint is how we get framework-adjacent safety without a framework.
Best Practices
- Extend
eslint:recommendedas the floor and layer project-specific rules (module scoping, no console.log in production code) on top. - Run ESLint as a pre-commit hook and again in CI — local-only enforcement is inconsistently applied across developers.
- Fail the build on any ESLint error; allow warnings only with a documented, reviewed reason.
- Keep the config in sync with the JavaScript Standards (§18) — the linter is the automated enforcement of that section, not a separate concern.
Common Mistakes
- No ESLint config at all, leaving JS style entirely to individual developer/AI-tool defaults.
- A config so loose it only catches syntax errors, missing the actual bug classes (unused vars, implicit globals) that matter most.
- Disabling a rule inline (
// eslint-disable-next-line) to silence a real bug instead of fixing it. - Running ESLint locally but not wiring it into CI, so violations still reach main via a rushed PR.
Examples
{/} .eslintrc.json
{
"extends": ["eslint:recommended"],
"env": { "browser": true, "es2022": true },
"parserOptions": { "sourceType": "module" },
"rules": {
"no-unused-vars": "error",
"no-implicit-globals": "error",
"eqeqeq": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
}{/} no .eslintrc — every AI tool and developer applies a different implicit style,
{/} console.logs and unused vars ship silently to productionTeam Rules
- ESLint runs in CI on every PR; any error blocks merge.
- Inline
eslint-disablecomments require a one-line justification and are flagged in review. - The ESLint config is treated as the executable form of the JavaScript Standards section — changes to one should prompt review of the other.
- New JS files are linted automatically; there is no "exempt" file or folder.
AI Instructions
- Generate JavaScript that passes the project ESLint config without requiring disable comments.
- Never add an eslint-disable comment to work around a real issue — fix the underlying code instead.
- Match the module-scoped, no-implicit-global patterns the config enforces.
- If a generated pattern seems to require disabling a rule, flag it for human review rather than silently disabling.
Validation Checklist
Stylelint Rules
Enforcing the CSS Standards automatically, including the plain-CSS Theme Store rule.
Overview
Stylelint enforces the CSS Standards (§17) mechanically: no SCSS/LESS syntax, no !important without justification, consistent custom-property usage, and low, flat specificity via a max-nesting-depth rule.
Why It Matters
Theme Store explicitly restricts committed SCSS — Stylelint is what catches an AI tool defaulting to familiar SCSS nesting syntax before it becomes a submission-blocking problem, rather than a human noticing it in review after the fact.
Best Practices
- Extend
stylelint-config-standardand add project-specific rules for nesting depth, ID selectors, and custom-property naming. - Run Stylelint in CI on every PR and treat any error as a merge blocker, same as ESLint.
- Ban ID selectors and cap nesting depth to structurally prevent the specificity problems described in CSS Standards.
- Validate that no SCSS/LESS syntax is present as an explicit, named rule — not just implied by "using plain CSS."
Common Mistakes
- An AI tool defaulting to SCSS-style nesting (
&:hover) out of training-data habit, which is invalid plain CSS. - No cap on nesting depth, allowing deep selector chains that silently reintroduce the specificity problems CSS Standards warns against.
- Allowing ID selectors, which create specificity that is difficult to override without escalating to
!important. - Running Stylelint locally only, so CSS violations still land on main from a rushed contributor.
Examples
{/} .stylelintrc.json
{
"extends": "stylelint-config-standard",
"rules": {
"max-nesting-depth": 2,
"declaration-no-important": true,
"selector-max-id": 0,
"custom-property-pattern": "^[a-z][a-z0-9-]*$"
}
}.card {
&:hover { // SCSS nesting — not valid plain CSS, violates Theme Store rule and fails Stylelint
.title { color: red; }
}
}Team Rules
- Stylelint runs in CI on every PR; any error blocks merge.
- SCSS/LESS syntax detection is an explicit, named, non-negotiable rule.
- ID selectors are disallowed at the linter level, not just by convention.
- Nesting depth is capped and enforced automatically, not left to reviewer judgment.
AI Instructions
- Generate plain CSS only — never SCSS/LESS nesting syntax, variables, or mixins.
- Keep selector nesting shallow and avoid ID selectors entirely.
- Use CSS custom properties for design tokens, matching the naming pattern enforced by the linter.
- If a generated stylesheet would trigger a Stylelint error, fix it before presenting the output as final.
Validation Checklist
Prettier Rules
Formatting is automated and non-debatable — one config, every language, every contributor.
Overview
Prettier formats JS, JSON, CSS, and Markdown on save and pre-commit. Liquid formatting is handled by Shopify's own @shopify/prettier-plugin-liquid, extending the same pipeline to .liquid files so the whole repo has one consistent formatting story.
Why It Matters
Formatting debates in code review are pure overhead — they consume reviewer time on something a tool solves perfectly and permanently. Automating it removes an entire category of back-and-forth between human reviewers and AI-generated PRs.
Best Practices
- Install
@shopify/prettier-plugin-liquidso.liquidfiles are formatted with the same rigor as JS/CSS/JSON. - Run Prettier via a pre-commit hook (e.g.
lint-staged) so nothing unformatted reaches a PR in the first place. - Add a CI "format check" step (
prettier --check) as a safety net in case a hook was skipped. - Never manually override Prettier's output in review — if the format looks wrong, fix the config, not the individual file.
Common Mistakes
- Formatting JS/CSS/JSON with Prettier but leaving
.liquidfiles unformatted because the plugin wasn't installed. - Reviewers leaving formatting nitpick comments instead of trusting the automated check.
- Committing without the pre-commit hook installed locally, creating noisy formatting-only diffs in later PRs.
- Manually "fixing" Prettier's output style preferences, which just reintroduces inconsistency the tool exists to prevent.
Examples
{/} .prettierrc
{
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"plugins": ["@shopify/prettier-plugin-liquid"]
}{/} no .prettierrc, no plugin for Liquid —
{/} every contributor (and every AI tool) formats slightly differently, and Liquid files aren't formatted at allTeam Rules
- Prettier (with the Liquid plugin) runs pre-commit on every contributor's machine and again in CI.
- No manual formatting overrides — if the default output is undesirable, the config is changed, not the individual file.
- PRs with formatting-only diffs unrelated to the task are not merged mixed in with functional changes — they are separated.
- The Prettier config is versioned and reviewed the same as any other shared tooling config.
AI Instructions
- Generate code already matching this Prettier config — do not hand-format differently and rely on a later pass.
- Include .liquid files in the same formatting discipline as JS/CSS/JSON.
- Do not bundle unrelated formatting-only changes into a functional PR; keep them separate if they are needed.
Validation Checklist
Git Workflow
Branching, commit messages, and history hygiene — human or AI authored, same standard.
Overview
We use trunk-based development with short-lived feature branches (type/short-description), Conventional Commits for messages, and squash-merge to main to keep history linear and readable.
Why It Matters
AI-assisted development tends to produce many small, iterative commits during a session ("fix typo," "try again," "actually fix it"). A consistent branch/commit/merge strategy is what turns that noisy working history into a clean, reviewable, and revertible main branch.
Best Practices
- Branch names follow
type/short-description(feat/,fix/,chore/,refactor/) so intent is visible at a glance. - Commit messages follow Conventional Commits (
feat:,fix:,refactor:) so history is scannable and can drive automated changelogs. - Squash-merge feature branches into main — the iterative AI-assisted working history stays on the branch, main gets one clean commit per PR.
- Keep feature branches short-lived (days, not weeks) to minimize merge conflicts and review staleness.
Common Mistakes
- Generic branch names (
patch-1,update) that give no signal about what the branch contains. - Commit messages like "fix" or "updates" that are meaningless six months later during a git blame investigation.
- Merging (not squashing) a branch with 30 noisy "wip" commits directly into main's permanent history.
- Long-lived feature branches that drift far from main, creating large, risky merge conflicts.
Examples
git checkout -b feat/testimonial-carousel-section
// ... work happens, possibly many small commits ...
git commit -m "feat(sections): add testimonial carousel with schema blocks"
// squash-merged to main via PR, one clean commit in historygit checkout -b patch-1
git commit -m "updates"
git commit -m "fix"
git commit -m "actually fix"
git merge --no-ff {/} messy branch name, meaningless messages, noisy history preserved foreverTeam Rules
- Branch names follow the
type/short-descriptionconvention without exception. - Commit messages follow Conventional Commits format.
- All merges to main are squash-merges via reviewed PRs — no direct pushes to main.
- Feature branches are expected to merge or be closed within roughly one week to limit drift.
AI Instructions
- When creating branches or commits on behalf of a developer, follow the type/short-description and Conventional Commits conventions exactly.
- Do not push directly to main under any circumstance — always work on a branch and open a PR.
- Write commit messages that describe the change's intent and effect, not the process of arriving at it.
Validation Checklist
GitHub Automation
Every pull request runs the same automated gate, regardless of which tool or developer opened it.
Overview
A single GitHub Actions workflow runs on every PR: Theme Check, ESLint, Stylelint, Prettier check, Lighthouse CI, accessibility testing (axe-core), HTML validation, broken-link checks, our custom engineering rules (naming, folder structure, JSON schema validation), an AI review pass (§37), and performance budget validation. All stages must pass before human review is requested.
Why It Matters
Automating everything mechanically checkable is what lets human reviewers spend their limited time on architecture, UX, and judgment calls instead of re-deriving what a linter already knows. It is also what makes the multi-tool promise of this portal real: automation does not care which AI wrote the code.
| Stage | Tool | Blocking? |
|---|---|---|
| Liquid lint | Theme Check | Yes — errors block merge |
| JS lint | ESLint | Yes |
| CSS lint | Stylelint | Yes |
| Formatting | Prettier --check | Yes |
| Performance | Lighthouse CI (per page-type budget) | Yes |
| Accessibility | axe-core automated scan | Yes — critical/serious issues |
| Markup validity | HTML validator | Yes |
| Link integrity | Broken-link checker | Yes |
| Custom rules | Naming, folder structure, schema JSON validation | Yes |
| AI review | Automated AI reviewer (§37 checklist) | Advisory + flags for human attention |
| Performance budget | Bundle-size / asset-weight diff vs. main | Yes — regression blocks |
Best Practices
- Run cheap, fast checks (lint, format) before expensive ones (Lighthouse, accessibility scan) to fail fast and save CI minutes.
- Cache dependencies between CI runs to keep pipeline time low enough that it doesn't discourage frequent small PRs.
- Post CI results as PR comments/checks with direct links to failing details, not just a pass/fail badge.
- Treat the CI pipeline itself as production code — reviewed, tested, and versioned, not a one-off YAML file nobody maintains.
Common Mistakes
- Making expensive checks (Lighthouse) run before cheap ones (lint), wasting CI minutes on PRs that fail lint anyway.
- A pipeline that reports failures without actionable detail, forcing developers to dig through raw logs.
- Allowing a "merge despite failing checks" override to become routine instead of a rare, justified exception.
- Letting the CI config itself go unreviewed and undocumented, so nobody remembers why a given check exists.
Examples
# .github/workflows/pr-checks.yml
on: pull_request
jobs:
lint:
steps:
- run: npm run lint:eslint
- run: npm run lint:stylelint
- run: npx prettier --check .
- run: shopify theme check
quality:
needs: lint
steps:
- run: npm run lighthouse-ci
- run: npm run test:a11y
- run: npm run validate:html# no CI config — reviewers manually run checks locally, inconsistently, if at all,
# and nothing stops a broken PR from mergingTeam Rules
- No PR merges with a failing required CI check, regardless of who or what authored it.
- CI configuration changes go through the same PR review process as application code.
- Fast checks run before slow ones to keep feedback loops short.
- Any manual override of a failing check requires tech-lead approval and a written reason in the PR.
AI Instructions
- Assume every PR you help author will run through this full pipeline — proactively check for issues these stages would catch.
- Do not suggest bypassing or disabling a CI check to "unblock" a merge; fix the underlying issue instead.
- Reference specific CI stage names when discussing what still needs to pass on a given PR.
Validation Checklist
Pull Request Checklist
What every PR description must answer before a human reviewer opens the diff.
Overview
Every PR uses a fixed template covering: the requirement it satisfies, which workflow stages (§6) were completed, a summary of AI involvement and human review, screenshots/recordings of the change, and explicit confirmation of the automated checks in §35.
Why It Matters
A PR without context forces the reviewer to reverse-engineer intent from a diff — slow, error-prone, and especially risky when the author is an AI tool with no memory of prior conversation. The template makes intent, scope, and verification explicit up front.
Best Practices
- Fill out the PR template before requesting review, not after a reviewer asks for missing context.
- Link the specific portal section(s) a non-obvious implementation choice was based on.
- Keep PRs scoped to one requirement — a PR mixing an unrelated refactor with a feature is harder to review and revert.
- Attach before/after screenshots for any visual change, even small ones — it saves the reviewer a local checkout.
Common Mistakes
- An empty or one-line PR description ("fixes the thing") with no context for the reviewer.
- Bundling multiple unrelated changes into one PR, making it unreviewable and unrevertable as a unit.
- Omitting which parts were AI-generated, hiding information the reviewer needs to calibrate their scrutiny.
- Requesting review before confirming local lint/Theme Check actually pass, wasting a review cycle on preventable failures.
Team Rules
- The PR template is mandatory and is not stripped out or left blank.
- PRs are scoped to a single requirement; unrelated changes go in a separate PR.
- AI involvement is disclosed per-PR, including which tool and roughly which portion of the change.
- A PR is not marked "ready for review" until its own checklist items are self-confirmed by the author.
AI Instructions
- When drafting a PR description, fill out every checklist item accurately — do not leave placeholders.
- Clearly state which parts of the change you generated versus which were human-written or modified.
- List any assumptions you made during implementation explicitly, so a human can confirm or correct them.
- Do not mark a PR ready for review until you have verified lint/Theme Check pass locally.
Validation Checklist
AI Code Review Checklist
An automated first-pass reviewer that checks every PR against this entire portal before a human looks at it.
Overview
An AI reviewer runs on every PR as a GitHub Action, scoring the diff against twelve categories drawn directly from this portal: architecture, naming, performance, accessibility, Liquid quality, JavaScript quality, CSS quality, documentation, complexity, merchant experience, Theme Editor UX, and Theme Store compliance. It comments inline with specific, cited findings — it does not approve or block merges itself.
Why It Matters
A human reviewer skimming a large AI-generated diff is prone to the same failure mode described in AI Development Strategy (§8): missing a confidently-wrong assumption because the code "looks right." An AI reviewer, prompted specifically against this portal's standards, catches a different error distribution than a human skim does — the two together are stronger than either alone.
| Category | What the AI reviewer checks |
|---|---|
| Architecture | Correct primitive used (section/block/snippet) per §21; no duplicated components |
| Naming | Matches the Naming Conventions table (§20) exactly |
| Performance | Responsive images, deferred scripts, no render-blocking additions (§25, §26) |
| Accessibility | Semantic HTML, ARIA patterns, alt text, focus handling (§24) |
| Liquid quality | Nil-safety, whitespace control, locale usage (§15) |
| JavaScript quality | Null checks, progressive enhancement, module scoping (§18) |
| CSS quality | Plain CSS, custom properties, flat specificity (§17) |
| Documentation | Snippet params documented; non-obvious decisions explained (§23) |
| Complexity | File size, branching depth, opportunities to simplify (§14) |
| Merchant experience | Defaults, empty states, discoverability (§28) |
| Theme Editor UX | Input types, labels, help text, grouping (§27) |
| Theme Store compliance | Required templates, licensing, no disallowed patterns (§29) |
Best Practices
- Have the AI reviewer cite the specific portal section for every finding, so its feedback is checkable, not just asserted.
- Keep the AI reviewer advisory — it comments and flags, but a human always makes the final approve/request-changes decision.
- Feed the AI reviewer the diff plus the relevant portal sections as context, not the whole codebase, to keep findings focused and cheap.
- Periodically audit the AI reviewer's own findings for false positives/negatives and refine its prompt accordingly.
Common Mistakes
- Letting the AI reviewer auto-approve or auto-merge — it is a first pass, not a substitute for human accountability.
- Vague AI review comments ("this could be cleaner") with no specific portal citation or actionable fix.
- Ignoring AI reviewer findings by default because "it's just a bot," which defeats the purpose of running it.
- Never revisiting the AI reviewer's prompt/config as the portal itself evolves, letting its checks go stale.
Team Rules
- The AI reviewer never has merge or approve permissions — only comment permissions.
- Every AI review finding cites the specific portal section it is checking against.
- A human reviewer explicitly acknowledges (fixes or dismisses with reason) every AI reviewer finding before merge.
- The AI reviewer's prompt is updated whenever the portal's standards materially change.
AI Instructions
- When acting as the automated reviewer, cite the specific portal section for every finding you raise.
- Flag issues across all twelve categories systematically rather than focusing only on the most obvious ones.
- Never claim authority to approve or block a merge — present findings for human judgment.
- Prioritize findings that indicate a silent assumption or scope deviation, per AI Development Strategy (§8), not just style nitpicks.
Validation Checklist
Testing Strategy
What we test, at what layer, and why a Shopify theme needs a different mix than a typical web app.
Overview
Theme testing is weighted toward the layers that catch the failure modes actually common in Liquid theming: schema/JSON validation, visual regression across breakpoints and content states, accessibility automation, and manual QA in a real Theme Editor. Unit tests cover pure JS logic (price formatting, cart math) where they add real value.
Why It Matters
A Shopify theme has relatively little pure business logic to unit test compared to a typical application — most of the risk lives in rendering correctness across content states and merchant configurations, which unit tests don't naturally cover. Matching test investment to actual risk, rather than copying a generic testing pyramid, is what keeps testing effort worthwhile.
| Layer | What it covers | Tooling |
|---|---|---|
| Schema/JSON validation | Every section schema and template JSON is structurally valid | CI script, JSON schema |
| Unit tests | Pure JS logic: price formatting, cart math, utility functions | Vitest / Jest |
| Visual regression | Sections render correctly across breakpoints and content states | Playwright + screenshot diffing |
| Accessibility automation | Automated WCAG checks on every template | axe-core in CI |
| Manual Theme Editor QA | Real merchant-like interaction: add/remove/reorder blocks, edge-case content | Human tester, pre-merge and pre-release |
Best Practices
- Write unit tests for pure functions (price formatting, discount math) — they are cheap, fast, and catch real regressions.
- Use visual regression testing for sections, checked at minimum, typical, and maximum content states across breakpoints.
- Include accessibility automation in the same CI run as visual regression, not as a separate, easily-skipped step.
- Reserve manual QA for what automation genuinely cannot cover: real merchant workflows in the live Theme Editor.
Common Mistakes
- Trying to unit-test Liquid rendering directly instead of using visual regression, which is the actually appropriate tool for that layer.
- Skipping visual regression for "simple" sections that later break silently when a merchant adds an unusually long title.
- Treating manual QA as optional once automated tests pass, missing issues automation structurally cannot catch (editor usability).
- No test coverage at all for cart/pricing math, where a silent bug directly costs the merchant money.
Examples
import { formatPrice } from './price';
test('formats cents as currency', () => {
expect(formatPrice(1999, 'USD')).toBe('$19.99');
});
test('handles zero correctly', () => {
expect(formatPrice(0, 'USD')).toBe('$0.00');
});// No test file for price.js at all.
// The first time formatPrice(0, ...) is exercised is in production,
// on a free-gift-with-purchase line item.Team Rules
- All pure JS utility functions (pricing, cart math, formatting) have unit test coverage.
- Every section is visual-regression tested at minimum, typical, and maximum content states.
- Accessibility automation runs in the same CI pass as every other required check.
- Manual Theme Editor QA is required pre-release for any change touching merchant-configurable behavior.
AI Instructions
- When generating pure utility functions, generate accompanying unit tests covering edge cases (zero, negative, very large values).
- When generating a new section, flag that it needs visual regression coverage at minimum/typical/maximum content states.
- Do not claim a feature is fully tested unless the appropriate layer (unit, visual, accessibility, manual) has actually been addressed.
Validation Checklist
Release Process
Versioning, changelogs, and how a build goes from main to a Theme Store release.
Overview
Releases follow Semantic Versioning (MAJOR.MINOR.PATCH), each with a written changelog entry, tagged in git, and validated against the full pre-submission checklist (§29) before being packaged for Theme Store update or direct merchant distribution.
Why It Matters
Shopify Theme Store explicitly requires every release to include a version number and release notes — merchants need to know what changed before updating a live storefront. A disciplined release process is both a platform requirement and basic respect for merchants running this theme in production.
Best Practices
- Bump MAJOR for breaking schema/setting changes, MINOR for new features, PATCH for fixes — applied consistently, not by feel.
- Write changelog entries in merchant-facing language, explaining impact, not just linking an internal PR number.
- Run the full Theme Store pre-submission checklist (§29) before every release that will be submitted, not just the first one.
- Tag every release in git and keep release artifacts (the packaged theme zip) traceable back to the exact commit.
Common Mistakes
- Vague changelog entries ("bug fixes") that give a merchant no way to assess whether the update affects them.
- Renaming or removing a setting id in a MINOR release, silently breaking merchant customizations that a MAJOR bump should have signaled.
- Skipping the pre-submission checklist on incremental updates because "it's just a small fix."
- No git tag corresponding to a released version, making a production issue impossible to trace back to an exact build.
Examples
## 1.4.0 — 2026-07-03
### Added
- Testimonial carousel section with up to 8 blocks (#182)
### Fixed
- Product card price alignment on Safari mobile (#179)
### Changed
- Increased default section spacing scale for better readability (#175)## Update
- various fixes and improvements
{/} no version number, no specifics, merchant has no idea what actually changed or whether it affects their customizationsTeam Rules
- Every release has a semantic version number and a merchant-readable changelog entry.
- Breaking changes to schema/settings always trigger a MAJOR version bump.
- The pre-submission checklist (§29) runs before every release intended for Theme Store submission, regardless of size.
- Every release is tagged in git and traceable to an exact commit.
AI Instructions
- When proposing a version bump, correctly classify the change as MAJOR/MINOR/PATCH per semantic versioning rules.
- Draft changelog entries in clear, merchant-facing language, not internal engineering shorthand.
- Flag any schema/setting change that would require a MAJOR bump due to breaking merchant configurations.
Validation Checklist
Roadmap
Where this portal — and the theme it governs — goes from here.
Overview
This portal ships v1 with all 40 sections populated for kickoff. It is expected to grow: new sections as new problem areas emerge, deeper detail in existing sections as decisions are validated in production, and periodic pruning of anything that turns out not to matter in practice.
Why It Matters
A handbook that never changes after kickoff has either achieved perfect foresight (unlikely) or stopped being read (likely). Treating this as a living roadmap, with an explicit review cadence, is what keeps it the actual source of truth rather than a historical artifact from week one.
Best Practices
- Review this entire portal at a fixed cadence (quarterly, minimum) rather than only when something breaks.
- Add new sections when a recurring class of PR feedback reveals an undocumented standard, not before it's proven necessary.
- Prune or rewrite sections that turn out to be wrong in practice — a stale rule is worse than no rule, because it erodes trust in the rest of the document.
- Keep the roadmap itself honest about what is aspirational versus committed.
Common Mistakes
- Writing the portal once at kickoff and never revisiting it as the codebase and team learn.
- Adding speculative sections for problems that haven't actually occurred yet, bloating the document without adding real value.
- Letting contradictions accumulate between this portal and the actual codebase without reconciling either direction.
- No owner for portal maintenance, so updates only happen reactively and inconsistently.
Team Rules
- A named owner is responsible for portal maintenance and the quarterly review.
- Any recurring PR review comment that isn't already covered by the portal triggers a proposal to add or update a section.
- Portal changes go through the same PR review process as code — this document is versioned, not edited casually.
- The roadmap section is updated at each quarterly review to reflect actual progress, not left static.
AI Instructions
- When you notice a recurring pattern of questions or corrections not covered by this portal, suggest it as a candidate addition rather than silently improvising each time.
- Do not treat this portal as frozen — check for and respect updates rather than relying on a cached understanding from earlier in a session.
- Flag when a portal section appears to contradict current codebase reality, so a human can reconcile it.