{"title":"Theme snippets, blocks, and components","slug":"theme-snippets-blocks-components","url":"https://support.storeconnect.com/articles/theme-snippets-blocks-components","url_markdown":"https://support.storeconnect.com/articles/theme-snippets-blocks-components.md","subtitle":null,"summary":"Snippets are reusable template fragments included with the render tag, content blocks are CMS-configured elements rendered on pages, and components are snippets that reload asynchronously in response to JavaScript events.","type":"Developer_Documentation","video_url":"","keywords":"snippets, content blocks, components, render tag, component tag, async reload, reloadable components, block types, default tag, context tag, sc.cart-updated, liquid partials","last_modified":"2026-08-21T07:12:35+0000","body_markdown":"StoreConnect themes use three mechanisms for composing templates: **snippets** for reusable fragments, **content blocks** for CMS-managed page sections, and **components** for parts of the UI that need to update without a full page reload.\n\n## Snippets\n\nSnippets are reusable template fragments stored in the `snippets/` directory. Include them with the `{% render %}` tag.\n\n### Including snippets\n\n\n```liquid\n\n{% render \"header\" %}\n{% render \"products/card\", product: product, show_price: true %}\n{% render \"shared/loader\", active: true %}\n```\n\n\nVariables are passed as named parameters. Inside a snippet, only the passed variables are available — snippets have their own scope and cannot access variables from the calling template.\n\n### Declaring inputs with `{% default %}`\n\nEvery snippet should declare all its input parameters at the top using `{% default %}`. This documents what the snippet accepts, provides safe fallbacks, and makes the interface clear to anyone reading the file.\n\n\n```liquid\n\n{%- comment -%} snippets/products/card.liquid {%- endcomment -%}\n{% default product: nil %}\n{% default show_price: true %}\n{% default show_brand: false %}\n\n\u003cdiv class=\"product-card\"\u003e\n  \u003ch3\u003e{{ product.name }}\u003c/h3\u003e\n  {% if show_brand and product.brand %}\n    \u003cspan\u003e{{ product.brand.name }}\u003c/span\u003e\n  {% endif %}\n  {% if show_price %}\n    \u003cp\u003e{{ product.price | money }}\u003c/p\u003e\n  {% endif %}\n\u003c/div\u003e\n```\n\n\nThe `{% default %}` tag:\n- Documents the snippet's API at a glance\n- Prevents undefined variable errors — all variables have safe fallbacks\n- Signals required vs optional — `nil` defaults indicate required parameters, value defaults indicate optional ones\n- Does not override a value that was already passed by the caller\n\n### Snippet organization\n\nOrganize snippets into subdirectories. Reference them with path notation:\n\n\n```\n\nsnippets/\n├── header.liquid               → {% render \"header\" %}\n├── footer.liquid               → {% render \"footer\" %}\n├── flash.liquid                → {% render \"flash\" %}\n├── products/\n│   ├── card.liquid             → {% render \"products/card\" %}\n│   └── product/\n│       ├── price.liquid        → {% render \"products/product/price\" %}\n│       └── add_to_cart.liquid  → {% render \"products/product/add_to_cart\" %}\n├── checkout/\n│   ├── customer_information/\n│   └── shipping_information/\n└── shared/\n    └── page_header.liquid\n```\n\n\n### Common snippet patterns\n\n**Flash messages:**\n\n\n```liquid\n\n{%- comment -%} snippets/flash.liquid {%- endcomment -%}\n{% if current_flash.notice %}\n  \u003cdiv class=\"sc-notice\" role=\"status\"\u003e{{ current_flash.notice }}\u003c/div\u003e\n{% endif %}\n{% if current_flash.alert %}\n  \u003cdiv class=\"sc-alert\" role=\"alert\"\u003e{{ current_flash.alert }}\u003c/div\u003e\n{% endif %}\n{% if current_flash.error %}\n  \u003cdiv class=\"sc-error\" role=\"alert\"\u003e{{ current_flash.error }}\u003c/div\u003e\n{% endif %}\n```\n\n\n**Form errors:**\n\n\n```liquid\n\n{%- comment -%} snippets/form_errors.liquid {%- endcomment -%}\n{% default errors: nil %}\n{% if errors.size \u003e 0 %}\n  \u003cdiv class=\"sc-form-errors\" role=\"alert\"\u003e\n    \u003cul\u003e\n      {% for error in errors %}\n        \u003cli\u003e{{ error }}\u003c/li\u003e\n      {% endfor %}\n    \u003c/ul\u003e\n  \u003c/div\u003e\n{% endif %}\n```\n\n\n### Snippets as functions\n\nSnippets can act as reusable functions that return structured data. The pattern uses `{% capture %}` to collect the snippet's output, then `| deserialize` to parse it into a usable object:\n\n\n```liquid\n\n{%- comment -%} snippets/helpers/price_breakdown.liquid {%- endcomment -%}\n{% default price: 0 %}\n{% default quantity: 1 %}\n{% default tax_rate: 0.1 %}\n\n{%- assign subtotal = price | times: quantity -%}\n{%- assign tax = subtotal | times: tax_rate -%}\n{%- assign total = subtotal | plus: tax -%}\n{%- new Map result = '{}' -%}\n{%- assign result = result | set_key: \"subtotal\", subtotal | set_key: \"tax\", tax | set_key: \"total\", total -%}\n{{ result | json }}\n```\n\n\n\n```liquid\n\n{%- capture raw -%}\n  {%- render \"helpers/price_breakdown\", price: product.price, quantity: 3, tax_rate: 0.1 -%}\n{%- endcapture -%}\n{%- assign breakdown = raw | strip | deserialize -%}\n\n\u003cp\u003eSubtotal: {{ breakdown.subtotal | money }}\u003c/p\u003e\n\u003cp\u003eTotal: {{ breakdown.total | money }}\u003c/p\u003e\n```\n\n\n:::note\nNever load a snippet into itself — this creates an infinite loop and will cause a template error.\n:::\n\n## Content blocks\n\nContent blocks are structured content elements configured in the CMS and rendered on pages. They allow store managers to build page layouts without editing templates.\n\n### How blocks work\n\n1. A store manager adds content blocks to a page in the CMS (for example, a slideshow, text section, or featured products block).\n2. Each block has a **type** that maps to a block template in the `blocks/` directory.\n3. The page template renders blocks via `{{ current_page.body_content }}`.\n\n### Block types\n\n| Block type | Template | Description |\n|------------|----------|-------------|\n| `text` | `blocks/text.liquid` | Rich text content |\n| `html` | `blocks/html.liquid` | Raw HTML content |\n| `image` | `blocks/image.liquid` | Single image with optional link |\n| `image_beside_text` | `blocks/image_beside_text.liquid` | Image alongside text |\n| `image_text_overlay` | `blocks/image_text_overlay.liquid` | Image with text overlay |\n| `video` | `blocks/video.liquid` | Video embed |\n| `media` | `blocks/media.liquid` | Generic media content |\n| `slideshow` | `blocks/slideshow.liquid` | Image carousel |\n| `container` | `blocks/container.liquid` | Container for nested blocks |\n| `featured_products` | `blocks/featured_products.liquid` | Product listing |\n| `featured_categories` | `blocks/featured_categories.liquid` | Category listing |\n| `featured_category_products` | `blocks/featured_category_products.liquid` | Products from a category |\n| `featured_articles` | `blocks/featured_articles.liquid` | Article listing |\n| `featured_pages` | `blocks/featured_pages.liquid` | Page listing |\n\n### Block template variables\n\nInside a block template, the `content_block` variable provides access to the block's data:\n\n\n```liquid\n\n{%- comment -%} blocks/text.liquid {%- endcomment -%}\n\u003csection class=\"SC-ContentBlock SC-ContentBlock-text\"\u003e\n  {% if content_block.title != blank %}\n    \u003ch2\u003e{{ content_block.title }}\u003c/h2\u003e\n  {% endif %}\n  \u003cdiv class=\"sc-rich-text\"\u003e\n    {{ content_block.body }}\n  \u003c/div\u003e\n\u003c/section\u003e\n```\n\n\n### ContentBlock drop properties\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `title` | String | Block title |\n| `subtitle` | String | Block subtitle |\n| `body` | String | Rich text body content |\n| `identifier` | String | Unique identifier |\n| `block_type` | String | Block type name |\n| `image` | ImageDrop | Associated image |\n| `images` | Array | Collection of images |\n| `items` | Array | Collection of content items (products, articles, etc.) |\n| `link_url` | String | Optional link URL |\n| `link_text` | String | Optional link text |\n| `css_class` | String | Custom CSS class |\n| `data` | Hash | Custom data fields |\n\n### Rendering blocks on pages\n\nThe most common approach is to render all blocks assigned to a page via `body_content`:\n\n\n```liquid\n\n{%- comment -%} pages/home.liquid {%- endcomment -%}\n{{ current_page.body_content }}\n```\n\n\nYou can also access individual blocks by identifier:\n\n\n```liquid\n\n{{ all_content_blocks.hero-banner.body }}\n```\n\n\n### Product content blocks\n\nProducts have their own content block sections:\n\n\n```liquid\n\n{{ current_product.features_content }}\n{{ current_product.specifications_content }}\n{{ current_product | downloads_content_blocks }}\n{{ current_product | warranty_content_blocks }}\n{{ current_product | support_content_blocks }}\n```\n\n\n## Components\n\nComponents are template fragments that reload asynchronously without a full page refresh. They respond to JavaScript events dispatched on the `document` object.\n\n### The `{% component %}` tag\n\n\n```liquid\n\n{% component \"component_name\" [, reload: \"event1 event2\", lazy: true, param: value] %}\n```\n\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| (first argument) | String | Component template path (relative to `components/`) |\n| `reload` | String | Space-separated list of JavaScript events that trigger a reload |\n| `lazy` | Boolean | If true, component loads asynchronously on page load |\n| Additional parameters | Any | Passed as variables to the component template |\n\n### Common component examples\n\n\n```liquid\n\n{%- comment -%} Cart component that reloads when cart is updated {%- endcomment -%}\n{% component \"cart\", reload: \"sc.cart-updated\" %}\n\n{%- comment -%} Cart badge in the header {%- endcomment -%}\n{% component \"cart-menu\", reload: \"sc.cart-updated\" %}\n\n{%- comment -%} Checkout payment section, reloads when vouchers change {%- endcomment -%}\n{% component \"checkout/payment_information/page\", reload: \"sc.voucher-applied sc.voucher-removed\" %}\n```\n\n\n### The `reloaded` variable\n\nInside a component template, `reloaded` is `true` when the component is being re-rendered via AJAX (not on initial page load). Use this for conditional rendering:\n\n\n```liquid\n\n{%- comment -%} components/cart.liquid {%- endcomment -%}\n\u003cdiv class=\"cart-items\"\u003e\n  {% for item in current_cart.items %}\n    \u003cdiv class=\"cart-item\"\u003e\n      {{ item.product.name }} — {{ item.quantity }}\n    \u003c/div\u003e\n  {% endfor %}\n\u003c/div\u003e\n```\n\n\n### The `{% context %}` tag\n\nThe `{% context %}` tag sets variables scoped to a component that persist across reloads:\n\n\n```liquid\n\n{% context product_id: current_product.id, show_details: true %}\n```\n\n\nContext variables:\n- Are scoped to the component and do not leak to parent or sibling components\n- Are available immediately after the `{% context %}` tag\n- Persist when the component reloads\n\n### Built-in events\n\n| Event | Dispatched when |\n|-------|-----------------|\n| `sc.cart-updated` | Cart contents change (add, remove, update quantity) |\n| `sc.voucher-applied` | A voucher or promo code is applied |\n| `sc.voucher-removed` | A voucher or promo code is removed |\n\n### Dispatching custom events\n\nTrigger a component reload from your own JavaScript:\n\n```javascript\n\n// Dispatch a built-in event\ndocument.dispatchEvent(new CustomEvent('sc.cart-updated'));\n\n// Dispatch a custom event\ndocument.dispatchEvent(new CustomEvent('wishlist-updated'));\n```\n\nListen for custom events in a component:\n\n\n```liquid\n\n{% component \"wishlist-count\", reload: \"wishlist-updated\" %}\n```\n\n\n### Lazy-loading components\n\nComponents with `lazy: true` load asynchronously after the initial page render:\n\n\n```liquid\n\n{% component \"heavy-widget\", reload: \"widget-updated\", lazy: true %}\n```\n\n\nThe page renders immediately with a placeholder, then the component content loads via AJAX. Use lazy loading for heavy components, components that need fresh data on every view, or below-the-fold content.\n\n### How component reloading works\n\n1. The `{% component %}` tag generates a unique nonce and wraps the component output in a `\u003cdiv\u003e` with that nonce as an identifier.\n2. When a JavaScript event fires matching the component's `reload` list, the platform fetches `GET /async/component/:nonce` for each matching component.\n3. The server re-renders the component template and returns the HTML.\n4. The built-in `liquid-components.js` script replaces the component's DOM content with the new HTML.\n\n### Cart with header badge — a common pattern\n\nBoth the header badge and the cart page reload when the cart changes:\n\n\n```liquid\n\n{%- comment -%} In the layout header snippet {%- endcomment -%}\n{% component \"cart-menu\", reload: \"sc.cart-updated\" %}\n\n{%- comment -%} On the cart page {%- endcomment -%}\n{% component \"cart\", reload: \"sc.cart-updated\" %}\n```\n\n\nThis keeps the header badge count in sync with the cart page automatically."}