Skip to content
Log in

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

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

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

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

{% if content_block.title != blank %}

{{ content_block.title }}

{% endif %}
{{ content_block.body }}

```

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

{% for item in current_cart.items %}
{{ item.product.name }} — {{ item.quantity }}
{% endfor %}

```

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.

Was this article helpful?

Was this article helpful?