# Theme snippets, blocks, and components

Source: https://support.storeconnect.com/articles/theme-snippets-blocks-components · Last modified 21 August 2026

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 %}

<div class="product-card">
  <h3>{{ product.name }}</h3>
  {% if show_brand and product.brand %}
    <span>{{ product.brand.name }}</span>
  {% endif %}
  {% if show_price %}
    <p>{{ product.price | money }}</p>
  {% endif %}
</div>
```


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 %}
  <div class="sc-notice" role="status">{{ current_flash.notice }}</div>
{% endif %}
{% if current_flash.alert %}
  <div class="sc-alert" role="alert">{{ current_flash.alert }}</div>
{% endif %}
{% if current_flash.error %}
  <div class="sc-error" role="alert">{{ current_flash.error }}</div>
{% endif %}
```


**Form errors:**


```liquid

{%- comment -%} snippets/form_errors.liquid {%- endcomment -%}
{% default errors: nil %}
{% if errors.size > 0 %}
  <div class="sc-form-errors" role="alert">
    <ul>
      {% for error in errors %}
        <li>{{ error }}</li>
      {% endfor %}
    </ul>
  </div>
{% 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 -%}

<p>Subtotal: {{ breakdown.subtotal | money }}</p>
<p>Total: {{ breakdown.total | money }}</p>
```


:::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

1. A store manager adds content blocks to a page in the CMS (for example, a slideshow, text section, or featured products block).
2. Each block has a **type** that maps to a block template in the `blocks/` directory.
3. 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 -%}
<section class="SC-ContentBlock SC-ContentBlock-text">
  {% if content_block.title != blank %}
    <h2>{{ content_block.title }}</h2>
  {% endif %}
  <div class="sc-rich-text">
    {{ content_block.body }}
  </div>
</section>
```


### 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 -%}
<div class="cart-items">
  {% for item in current_cart.items %}
    <div class="cart-item">
      {{ item.product.name }} — {{ item.quantity }}
    </div>
  {% endfor %}
</div>
```


### 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

1. The `{% component %}` tag generates a unique nonce and wraps the component output in a `<div>` with that nonce as an identifier.
2. When a JavaScript event fires matching the component's `reload` list, the platform fetches `GET /async/component/:nonce` for each matching component.
3. The server re-renders the component template and returns the HTML.
4. The built-in `liquid-components.js` script 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.

---

## Follow StoreConnect

- [Email Newsletter](https://getstoreconnect.com/c/lp-newsletter)
- [LinkedIn Newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7444956928444862464)
- [YouTube](https://www.youtube.com/channel/UCngKdP2x8l1wcbAKW3tvU8g)
- [LinkedIn](https://www.linkedin.com/company/storeconnect)
- [X / Twitter](https://x.com/storeconnecthq)

## Popular Links

- [Partners](https://getstoreconnect.com/partners)
- [News](https://getstoreconnect.com/articles/news)
- [Events](https://getstoreconnect.com/articles/events)
- [Feature Comparison](https://getstoreconnect.com/how-we-compare)
- [Download a free trial](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000FMkeKUAT)
- [Book a Demo](https://getstoreconnect.com/contact)

## Documentation

- [Help documentation](https://support.storeconnect.com/help-documentation)
- [AI agents](https://support.storeconnect.com/ai)
- [Videos & tutorials](https://support.storeconnect.com/videos-tutorials)
- [Developer reference](https://support.storeconnect.com/developer-reference)
- [Release notes](https://support.storeconnect.com/release-notes)
- [Troubleshooting](https://support.storeconnect.com/troubleshooting)
- [Trust Center](https://trust.getstoreconnect.com/)
- [Status Page](https://status.storeconnect.com/)

## Contact

- info@getstoreconnect.com
- US +1 415 745 3230
- AUS +61 2 8365 2308

100 S Ashley Dr, Suite 600-2461
Tampa FL 33602-600 USA

Level 22, Sydney Place
180 George Street
Sydney, NSW, 2000, AUS

---

StoreConnect Support — https://support.storeconnect.com/articles/theme-snippets-blocks-components