Skip to content

Theme Architecture

Directory What lives there
sections/ Full-width page components. Each has a {% schema %} and appears in the customizer.
blocks/ Reusable components that slot into sections. Own schema, own settings.
snippets/ Shared Liquid fragments rendered with {% render %}. No schema — they take parameters.
frontend/ JS and CSS compiled by Vite. Alpine.js components live in frontend/entrypoints/js/alpine/.
locales/ Translations. en.default.schema.json holds every customizer label.
templates/ JSON files defining which sections appear on each page type.
config/ Theme-wide settings.

The distinction matters, and it’s easy to get wrong.

Sections are the top-level building blocks a merchant adds to a page. They own layout and page-level settings — padding, background, colour.

Blocks are merchant-arrangeable content inside a section. They’re defined once in blocks/ and referenced by type, never redefined per section.

Snippets are developer-level code reuse. If two blocks render the same markup, that markup belongs in a snippet both of them call.

A static block is a theme block rendered individually rather than as part of the merchant-reorderable collection. It’s how a section pins a specific piece of content to a specific spot in its layout — a title above a product grid, say.

Each static block id may only be rendered once per section. When the same content needs to appear in two places (desktop bar and mobile drawer, for instance), capture the output first and echo the captured variable in both places:

{%- capture nav %}{% content_for 'block', type: 'header-nav', id: 'nav' %}{% endcapture -%}
<div class="desktop">{{ nav }}</div>
<div class="mobile">{{ nav }}</div>

Vite with vite-plugin-shopify. Assets are hashed on build and referenced through snippets/vite-tag.liquid.

Terminal window
npm run dev # Vite + Shopify theme dev together
npm run build # Production asset build

The Shopify dev command runs with --theme-editor-sync, which pulls customizer changes down into local templates/*.json files. Expect those files to change under you while the dev server is running; they’re owned by the editor, not by hand-editing.

Alpine.js for interactivity — dropdowns, drawers, toggles. Components are registered in frontend/entrypoints/js/alpine/.

Tailwind v4 with CSS-first config. The theme’s tokens live in frontend/css/tailwind-theme.css under @theme. See Conventions for the rules on colours and spacing.

React is used only for the cart drawer, mounted to #cart-drawer.