Theme Section Blocks
How to design, render, and edit section blocks in Cartly themes — the developer reference for theme authoring.
Overview
A section is a top-level page unit (hero, featured-collection, footer). A block is a sub-unit within a section — one editorial card, one footer column, one accordion panel. Blocks exist because sections often need a repeating UI of variable count where structure is tightly coupled to the section's own layout.
When to use what: use a setting for a single toggle; use a block for repeating UI inside one section; use a separate section for content the merchant might add or remove independently.
Schema Declaration
Blocks are declared in {% schema %} alongside section settings:
{
"name": "Testimonials",
"blocks": [
{
"type": "testimonial",
"name": "Testimonial",
"limit": 6,
"settings": [
{ "type": "text", "id": "author", "label": "Author name" },
{ "type": "textarea", "id": "quote", "label": "Quote" }
]
}
],
"max_blocks": 6,
"presets": [{ "name": "Testimonials", "blocks": [{ "type": "testimonial" }] }]
}Each block schema entry has: type (discriminator), name (display label in the editor accordion), settings (same shape as section settings), and optional limit (per-type max). max_blocks caps total count across all types. presets[].blocks pre-populates blocks when the section is first added.
Instance JSON Shape
Section instances in templates/*.json carry:
{
"sections": {
"testimonials": {
"type": "testimonials",
"settings": {},
"blocks": {
"block_1": { "type": "testimonial", "settings": { "author": "Jane D.", "quote": "Best store!" } },
"block_2": { "type": "testimonial", "settings": { "author": "Mark T.", "quote": "Great service!" } }
},
"block_order": ["block_1", "block_2"]
}
}
}block_order (singular, Shopify-compatible) controls render order. The visual editor always writes block_order; it reads either for backwards compatibility.
Liquid Rendering
{% for block in section.blocks %}
{% if block.type == 'testimonial' %}
<div {{ block.shopify_attributes }}>
<p>{{ block.settings.quote | escape }}</p>
<cite>{{ block.settings.author | escape }}</cite>
</div>
{% endif %}
{% endfor %}The renderer assembles section.blocks from the instance's blocks map in block_order. Blocks not listed in block_order are appended at the tail (lossless). Discriminate on block.type when a section accepts multiple block types. Always emit {{ block.shopify_attributes }} on the block's outer element — the visual editor's bridge uses this attribute to locate and highlight the block.
Visual Editor — Block Panel
When a merchant selects a section in the visual editor, the right panel shows an accordion below the section settings — one row per block in block_order. Expanding a row reveals the block's settings rendered through the same field-renderer as section settings. The expanded row dispatches a cartly:block:select postMessage to the preview iframe; editor-preview.js catches it, finds the matching [data-block-id] inside the section container, scrolls to it, and applies the .cartly-block-highlight CSS animation.
Lazy mounting: only the expanded row's settings tree exists in the DOM. Collapsed rows have zero DOM overhead.
What the visual editor supports: editing any block's settings fields, live preview of changes.
What is in the code editor only: adding blocks, deleting blocks, reordering blocks, changing block types, adjusting block_order.
Code Editor — Structural Changes
Open /admin/themes/<id>/code and edit the template JSON directly:
- Add a block: add an entry to
sections.<id>.blocksmap and push the key toblock_order. - Remove a block: delete from both the blocks map and the order array.
- Reorder blocks: rearrange entries in
block_order. - Visibility per block: not natively supported at the renderer level. Use
{% if block.settings.show %}inside the Liquid loop with acheckboxsetting.
Depth Limit
Cartly Liquid does not support blocks-within-blocks. If your schema accidentally declares blocks inside a block entry, the parser logs a console.warn and ignores the nested declaration. There is no runtime error — the mistake surfaces during local theme development in the browser console.
Best Practices
- Always ship at least one preset with default block content matching the design mockup.
- Pair
max_blockswith the Liquid template's UI capacity (e.g. an editorial-pair section with a 2-column grid:max_blocks: 2). - Emit
{{ block.shopify_attributes }}on every block's outer element — required for the visual editor highlight bridge. - Apply
| escapeto all merchant-controlled string values interpolated into HTML attributes (THEME-AUTHORING-STANDARDS M-6). - Do not nest blocks — use separate sections for independently manageable content.