Theme snippets, blocks, and components
On this page
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.
Snippets
Snippets are reusable template fragments stored in the snippets/ directory. Include them with the {% render %} tag.
Including snippets
```liquid
{% render “header” %} {% render “products/card”, product: product, show_price: true %} {% render “shared/loader”, active: true %} ```
Variables 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.
Declaring inputs with {% default %}
Every 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.
```liquid
{%- comment -%} snippets/products/card.liquid {%- endcomment -%} {% default product: nil %} {% default show_price: true %} {% default show_brand: false %}
{{ product.name }}
{% if show_brand and product.brand %} {{ product.brand.name }} {% endif %} {% if show_price %}{{ product.price | money }}
{% endif %}```
The {% default %} tag:
- Documents the snippet’s API at a glance
- Prevents undefined variable errors — all variables have safe fallbacks
- Signals required vs optional — nil defaults indicate required parameters, value defaults indicate optional ones
- Does not override a value that was already passed by the caller
Snippet organization
Organize snippets into subdirectories. Reference them with path notation:
```
snippets/ ├── header.liquid → {% render “header” %} ├── footer.liquid → {% render “footer” %} ├── flash.liquid → {% render “flash” %} ├── products/ │ ├── card.liquid → {% render “products/card” %} │ └── product/ │ ├── price.liquid → {% render “products/product/price” %} │ └── add_to_cart.liquid → {% render “products/product/add_to_cart” %} ├── checkout/ │ ├── customer_information/ │ └── shipping_information/ └── shared/ └── page_header.liquid ```
Common snippet patterns
Flash messages:
```liquid
{%- comment -%} snippets/flash.liquid {%- endcomment -%} {% if current_flash.notice %}
{% endif %} {% if current_flash.alert %}
{% endif %} {% if current_flash.error %}
{% endif %} ```
Form errors:
```liquid
{%- comment -%} snippets/form_errors.liquid {%- endcomment -%} {% default errors: nil %} {% if errors.size > 0 %}
-
{% for error in errors %}
- {{ error }} {% endfor %}
{% endif %} ```
Snippets as functions
Snippets 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:
```liquid
{%- comment -%} snippets/helpers/price_breakdown.liquid {%- endcomment -%} {% default price: 0 %} {% default quantity: 1 %} {% default tax_rate: 0.1 %}
{%- assign subtotal = price | times: quantity -%} {%- assign tax = subtotal | times: tax_rate -%} {%- assign total = subtotal | plus: tax -%} {%- new Map result = ‘{}’ -%} {%- assign result = result | set_key: “subtotal”, subtotal | set_key: “tax”, tax | set_key: “total”, total -%} {{ result | json }} ```
```liquid
{%- capture raw -%} {%- render “helpers/price_breakdown”, price: product.price, quantity: 3, tax_rate: 0.1 -%} {%- endcapture -%} {%- assign breakdown = raw | strip | deserialize -%}
Subtotal: {{ breakdown.subtotal | money }}
Total: {{ breakdown.total | money }}
```
:::note Never load a snippet into itself — this creates an infinite loop and will cause a template error. :::
Content blocks
Content blocks are structured content elements configured in the CMS and rendered on pages. They allow store managers to build page layouts without editing templates.
How blocks work
- A store manager adds content blocks to a page in the CMS (for example, a slideshow, text section, or featured products block).
- Each block has a type that maps to a block template in the
blocks/directory. - The page template renders blocks via
{{ current_page.body_content }}.
Block types
| Block type | Template | Description |
|---|---|---|
text |
blocks/text.liquid |
Rich text content |
html |
blocks/html.liquid |
Raw HTML content |
image |
blocks/image.liquid |
Single image with optional link |
image_beside_text |
blocks/image_beside_text.liquid |
Image alongside text |
image_text_overlay |
blocks/image_text_overlay.liquid |
Image with text overlay |
video |
blocks/video.liquid |
Video embed |
media |
blocks/media.liquid |
Generic media content |
slideshow |
blocks/slideshow.liquid |
Image carousel |
container |
blocks/container.liquid |
Container for nested blocks |
featured_products |
blocks/featured_products.liquid |
Product listing |
featured_categories |
blocks/featured_categories.liquid |
Category listing |
featured_category_products |
blocks/featured_category_products.liquid |
Products from a category |
featured_articles |
blocks/featured_articles.liquid |
Article listing |
featured_pages |
blocks/featured_pages.liquid |
Page listing |
Block template variables
Inside a block template, the content_block variable provides access to the block’s data:
```liquid
{%- comment -%} blocks/text.liquid {%- endcomment -%}
{{ content_block.title }}
{% endif %}```
ContentBlock drop properties
| Property | Type | Description |
|---|---|---|
title |
String | Block title |
subtitle |
String | Block subtitle |
body |
String | Rich text body content |
identifier |
String | Unique identifier |
block_type |
String | Block type name |
image |
ImageDrop | Associated image |
images |
Array | Collection of images |
items |
Array | Collection of content items (products, articles, etc.) |
link_url |
String | Optional link URL |
link_text |
String | Optional link text |
css_class |
String | Custom CSS class |
data |
Hash | Custom data fields |
Rendering blocks on pages
The most common approach is to render all blocks assigned to a page via body_content:
```liquid
{%- comment -%} pages/home.liquid {%- endcomment -%} {{ current_page.body_content }} ```
You can also access individual blocks by identifier:
```liquid
{{ all_content_blocks.hero-banner.body }} ```
Product content blocks
Products have their own content block sections:
```liquid
{{ current_product.features_content }} {{ current_product.specifications_content }} {{ current_product | downloads_content_blocks }} {{ current_product | warranty_content_blocks }} {{ current_product | support_content_blocks }} ```
Components
Components are template fragments that reload asynchronously without a full page refresh. They respond to JavaScript events dispatched on the document object.
The {% component %} tag
```liquid
{% component “component_name” [, reload: “event1 event2”, lazy: true, param: value] %} ```
| Parameter | Type | Description |
|---|---|---|
| (first argument) | String | Component template path (relative to components/) |
reload |
String | Space-separated list of JavaScript events that trigger a reload |
lazy |
Boolean | If true, component loads asynchronously on page load |
| Additional parameters | Any | Passed as variables to the component template |
Common component examples
```liquid
{%- comment -%} Cart component that reloads when cart is updated {%- endcomment -%} {% component “cart”, reload: “sc.cart-updated” %}
{%- comment -%} Cart badge in the header {%- endcomment -%} {% component “cart-menu”, reload: “sc.cart-updated” %}
{%- comment -%} Checkout payment section, reloads when vouchers change {%- endcomment -%} {% component “checkout/payment_information/page”, reload: “sc.voucher-applied sc.voucher-removed” %} ```
The reloaded variable
Inside 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:
```liquid
{%- comment -%} components/cart.liquid {%- endcomment -%}
```
The {% context %} tag
The {% context %} tag sets variables scoped to a component that persist across reloads:
```liquid
{% context product_id: current_product.id, show_details: true %} ```
Context variables:
- Are scoped to the component and do not leak to parent or sibling components
- Are available immediately after the {% context %} tag
- Persist when the component reloads
Built-in events
| Event | Dispatched when |
|---|---|
sc.cart-updated |
Cart contents change (add, remove, update quantity) |
sc.voucher-applied |
A voucher or promo code is applied |
sc.voucher-removed |
A voucher or promo code is removed |
Dispatching custom events
Trigger a component reload from your own JavaScript:
```javascript
// Dispatch a built-in event document.dispatchEvent(new CustomEvent(‘sc.cart-updated’));
// Dispatch a custom event document.dispatchEvent(new CustomEvent(‘wishlist-updated’)); ```
Listen for custom events in a component:
```liquid
{% component “wishlist-count”, reload: “wishlist-updated” %} ```
Lazy-loading components
Components with lazy: true load asynchronously after the initial page render:
```liquid
{% component “heavy-widget”, reload: “widget-updated”, lazy: true %} ```
The 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.
How component reloading works
- The
{% component %}tag generates a unique nonce and wraps the component output in a<div>with that nonce as an identifier. - When a JavaScript event fires matching the component’s
reloadlist, the platform fetchesGET /async/component/:noncefor each matching component. - The server re-renders the component template and returns the HTML.
- The built-in
liquid-components.jsscript replaces the component’s DOM content with the new HTML.
Cart with header badge — a common pattern
Both the header badge and the cart page reload when the cart changes:
```liquid
{%- comment -%} In the layout header snippet {%- endcomment -%} {% component “cart-menu”, reload: “sc.cart-updated” %}
{%- comment -%} On the cart page {%- endcomment -%} {% component “cart”, reload: “sc.cart-updated” %} ```
This keeps the header badge count in sync with the cart page automatically.
Was this article helpful?
Thanks for your feedback! It helps us improve our docs.