Theme Architecture
Directory layout
Section titled “Directory layout”| Directory | What lives there |
|---|---|
sections/ |
Full-width page components. Each has a {% schema %} and appears in the customizer. 50 files. |
blocks/ |
Reusable components that slot into sections. Own schema, own settings. 72 files. |
snippets/ |
Shared Liquid fragments rendered with {% render %}. No schema — they take parameters. ~99 files. |
frontend/ |
JS and CSS compiled by Vite. Alpine.js components, React cart, and Tailwind CSS. |
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 — settings_schema.json, settings_data.json, markets.json. |
Sections vs. blocks vs. snippets
Section titled “Sections vs. blocks vs. snippets”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.
Static blocks
Section titled “Static blocks”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.
{% content_for 'block', type: 'title', id: 'section-title' %}{% content_for 'block', type: 'buttons', id: 'cta-buttons' %}Static blocks use the same files from blocks/ — only the rendering differs.
You can pass params to override block settings with dynamic values:
{% content_for 'block', type: 'title', id: 'hero-title', title: product.metafields.nama.hero_title.value %}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), capture the output first:
{%- capture nav %}{% content_for 'block', type: 'header-nav', id: 'nav' %}{% endcapture -%}
<div class="desktop">{{ nav }}</div><div class="mobile">{{ nav }}</div>The standard section pattern
Section titled “The standard section pattern”Almost every section follows this structure:
- Static blocks for pinned header content (title, tag, richtext)
- Theme blocks for repeatable content via
{% content_for 'blocks' %} - Each block renders itself — the section never reads block settings
For hide-when-empty on PDP and collection pages, check the metafield directly
rather than relying on section.blocks.size (which doesn’t work with theme
blocks).
Product templates
Section titled “Product templates”| Template | Used for |
|---|---|
product.json (default) |
Flagship juicers — full PDP with all blocks |
product.parts.json |
Parts and warranty products — simplified layout |
product.mid-tier.json |
Accessories, books, apparel — mid-complexity PDP |
Collection templates
Section titled “Collection templates”| Template | Used for |
|---|---|
collection.json |
Core collection — auto-fill grid with metaobject editorial cards at configured positions |
collection.parts.json |
Parts collection — 4-column minimal grid |
collection.gift-guide.json |
Gift guide — stacked sections/blocks, no collection loop |
Build pipeline
Section titled “Build pipeline”Vite with vite-plugin-shopify. Assets are hashed on build and referenced
through snippets/vite-tag.liquid.
npm run dev # Vite + Shopify theme dev togethernpm run build # Production asset buildThe 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.
Frontend
Section titled “Frontend”Alpine.js for interactivity — dropdowns, drawers, toggles, filtering,
product forms. Components are registered in
frontend/entrypoints/js/alpine/ (14 components including cart, header,
collection filters, product form, quick add, and bundle group).
Tailwind v4 with CSS-first config. The theme’s tokens live in
frontend/entrypoints/css/theme.css under @theme. No tailwind.config.js —
everything is in CSS. See Conventions for the rules on colours
and spacing.
React is used only for the cart drawer, mounted to #cart-drawer. Components
live in frontend/entrypoints/js/cart/components/ — Cart, CartLineItems,
UpsellProduct, PromoCode, etc.
Splide.js for carousels — initialized via frontend/entrypoints/js/splide-init.js.
Metaobjects
Section titled “Metaobjects”The theme relies heavily on Shopify metaobjects for structured content that’s reused across pages:
bundle_offer— bundle product cards on PDPcollection_editorial_set/collection_editorial_card— editorial cards in collection gridsfaq_group/faq_item— reusable FAQ clustersfeatured_articles— curated article sets per productslides/social_slide_item— social media slider contenttext_image_split— text/image split content per producteducator— educator profile cardsarticle_topic— blog topic taxonomy (Fresh Press)product_card_images— custom card images per product
Product-level metafields in the nama.* namespace connect products to their
metaobjects.