Sections & Blocks
Sections and blocks are the composable building blocks of Shopify OS 2.0 themes. Sections define page-level components; blocks are content units that nest inside them.
Sections
A section is a self-contained Liquid file in theme/sections/ that combines markup with a {% schema %} definition. Kona has 24 sections:
Layout sections (loaded by theme.liquid directly):
header-- site header with navigation, search, cart iconfooter-- site footer with menus, social links, country selector
Cart sections:
cart-drawer-- slide-out cart drawercart-icon-bubble-- cart count badge in the headercart-live-region-text-- screen reader announcements for cart updatescart-subtotal-- cart total display
Main content sections (one per template type):
main-product,main-collection,main-search,main-blog,main-article,main-page,main-cart,main-list-collections,main-password-header,main-password-footer
Composable sections (can be added to any template via the editor):
image-banner,featured-collection,collection-list,email-signup-banner,contact-form,product-recommendations,section.liquid,islands-demo
How Sections Are Referenced
JSON templates reference sections by their filename (without .liquid):
{
"sections": {
"main": {
"type": "main-product"
},
"product-recommendations": {
"type": "product-recommendations"
}
},
"order": ["main", "product-recommendations"]
}The key (e.g., "main") is an arbitrary identifier used in the order array to control rendering sequence. The "type" value must match a filename in theme/sections/.
Layout sections are loaded differently -- they use the {% section %} tag directly in the layout file:
{%- comment -%} In theme/layout/theme.liquid {%- endcomment -%}
{% section 'header' %}
<main role="main" id="MainContent" tabindex="-1">
{{ content_for_layout }}
</main>
{% section 'footer' %}Blocks
Blocks are defined as separate Liquid files in theme/blocks/. Each block has its own {% schema %} and can be nested inside any section that accepts it. This is a key OS 2.0 pattern -- blocks are reusable across sections.
Theme Blocks (@theme)
When a section declares "blocks": [{ "type": "@theme" }] in its schema, it accepts any block that has a presets entry in its schema. These are called "theme blocks" and include:
heading-- configurable heading element (h1-h3 or div)text-- rich text contentbutton-- link or button with variant/size optionsimage-- responsive image displayprice-- product price with sale/compare-at supportproduct-card-- product card with nested blocks for image, title, pricecollection-card-- collection card with image and titlearticle-card-- blog article cardtab-- collapsible tab/accordiondetails-- expandable details element- And more (variant-picker, buy-buttons, email-signup, etc.)
Rendering Blocks in Sections
Sections render their blocks using two approaches:
Render all blocks with content_for 'blocks':
{%- comment -%} In theme/sections/image-banner.liquid {%- endcomment -%}
<div class="flex flex-col items-baseline gap-4 px-6 py-8">
{%- content_for 'blocks' -%}
</div>This renders every block assigned to the section, in the order defined by the template or merchant.
Render a specific block by type and ID:
{%- comment -%} In theme/sections/featured-collection.liquid {%- endcomment -%}
{%- content_for 'block', type: 'heading', id: 'heading' -%}This approach is used when a section needs precise control over where individual blocks appear in its layout.
Pass data to blocks using the closest mechanism:
{%- for product in section.settings.collection.products limit: section.settings.products_to_show -%}
{%- content_for 'block',
type: 'product-card',
id: 'static-product-card',
closest.product: product,
lazy_load: lazy_load,
wrapper_class: 'w-80'
-%}
{%- endfor -%}The closest.product parameter passes the current product to the block, which can then reference it in its settings as {{ closest.product }}.
Static Blocks
Blocks can be marked as "static": true in JSON templates. Static blocks cannot be removed or reordered by merchants in the theme editor -- they are fixed parts of the section's structure:
{
"type": "main-search",
"blocks": {
"heading": {
"type": "heading",
"static": true,
"id": "heading",
"settings": {
"heading": "Search",
"heading_level": "h1"
}
}
}
}This is useful for structural blocks like page headings that should always be present.
Nested Blocks
Blocks can contain other blocks, creating a tree structure. For example, a product-card block contains image, text, and price blocks:
{
"type": "product-card",
"static": true,
"settings": {
"product": "{{ closest.product }}"
},
"blocks": {
"heading": {
"type": "text",
"static": true,
"settings": {
"text": "<p>{{ closest.product.title }}</p>"
}
},
"price": {
"type": "price",
"static": true,
"id": "price",
"settings": {
"product": "{{ closest.product }}"
}
},
"image": {
"type": "image",
"static": true,
"id": "image",
"settings": {
"image": "{{ closest.product.featured_image }}"
}
}
}
}The parent block renders its children using the same content_for 'block' mechanism:
{%- comment -%} In theme/blocks/product-card.liquid {%- endcomment -%}
<div class="grid snap-start gap-6">
<div class="bg-primary/5 aspect-[4/5] overflow-hidden rounded">
{%- content_for 'block', type: 'image', id: 'image', lazy_load: lazy_load -%}
</div>
<div class="grid gap-1">
{%- content_for 'block', type: 'text', id: 'heading' -%}
{%- content_for 'block', type: 'price', id: 'price' -%}
</div>
</div>Schema Structure
Every section and block has a {% schema %} tag containing a JSON object. The schema registers the component with the Shopify theme editor.
Section Schema
{% schema %}
{
"name": "t:sections.image_banner.name",
"tag": "section",
"settings": [
{
"type": "image_picker",
"id": "image",
"label": "t:sections.image_banner.settings.image.label"
},
{
"type": "checkbox",
"id": "full_page_height_enable",
"default": true,
"label": "t:sections.image_banner.settings.full_page_height_enable.label"
}
],
"blocks": [{ "type": "@theme" }],
"presets": [
{
"name": "t:sections.image_banner.presets.name",
"category": "t:sections.image_banner.presets.category"
}
]
}
{% endschema %}Key properties:
| Property | Purpose |
|---|---|
name | Display name in the theme editor (uses t: translation key) |
tag | HTML wrapper element (e.g., "section", or null for no wrapper) |
settings | Array of configurable settings (types: image_picker, checkbox, text, select, range, collection, url, etc.) |
blocks | What block types the section accepts (@theme for any theme block, @app for app blocks, or specific types) |
presets | Default configurations that appear in the "Add section" picker in the editor |
Block Schema
{% schema %}
{
"name": "t:blocks.heading.name",
"tag": null,
"settings": [
{
"type": "inline_richtext",
"id": "heading",
"label": "t:blocks.heading.settings.heading.label",
"default": "t:blocks.heading.settings.heading.default"
}
],
"presets": [
{
"name": "t:blocks.heading.presets.name",
"category": "t:blocks.heading.presets.category"
}
]
}
{% endschema %}Blocks use "tag": null so they render without a wrapper element, giving the parent section full control over markup structure.
Translation Keys in Schemas
All human-readable strings in schemas use t: prefixed translation keys instead of hardcoded text. This ensures the theme editor is localized for all languages Shopify supports:
{
"name": "t:sections.featured_collection.name",
"label": "t:sections.featured_collection.settings.collection.label"
}These keys resolve to values defined in theme/locales/ JSON files. See Liquid Patterns for more on translation conventions.
Presets
Presets define default configurations for sections and blocks. When a merchant adds a new section from the theme editor, the preset determines the initial blocks and settings:
"presets": [
{
"name": "t:sections.image_banner.presets.name",
"category": "t:sections.image_banner.presets.category",
"blocks": {
"heading": {
"type": "heading",
"settings": {
"heading": "t:sections.image_banner.presets.blocks.heading.heading",
"heading_size": "xl",
"heading_level": "h2"
}
},
"text": {
"type": "text",
"settings": {
"text": "t:sections.image_banner.presets.blocks.text.text",
"text_size": "lg"
}
},
"button": {
"type": "button",
"settings": {
"button_label": "t:sections.image_banner.presets.blocks.button.button_label",
"button_variant": "link",
"button_size": "lg"
}
}
},
"block_order": ["heading", "text", "button"]
}
]Presets can include block_order to control the initial ordering of blocks.
Also See
- Templates -- how JSON templates reference sections
- Liquid Patterns -- LiquidDoc,
{% render %}, and translation conventions - Architecture Overview -- how sections connect to the islands hydration system