Examples
Six complete sections. Each is a real .storelib file that the test suite compiles, and runs through the same quality checks the builder applies to AI-written sections, on every change to the platform. If one of these stopped compiling, the change that broke it would not ship. Copy any of them into the code editor as sections/<type>.storelib and it works as shown.
Every example follows the same conventions, which are the library's own:
- The root reads the colour scheme with
data-scheme="{{ settings.color_scheme }}", and every colour comes from a--scheme-*variable. - Spacing is two
rangesettings, passed to CSS as custom properties on the root. container-type: inline-sizeon the root, a phone layout as the base, and@containerfor wider.- Ids are built from
{{ section.id }}, so each section can appear on a page twice. - Anything that moves stops under
prefers-reduced-motion. - Empty states sit behind
page.in_editor.
Sign-up card#
A sign-up box that sends every address to the creator's Subscribers with no script and no setup. Shows the explicit data-storelib-form="subscribe", a success message the creator can edit, a visually hidden label, and a form that stacks on a phone and sits in one row wider. See Forms.
<template>
<section class="su" data-scheme="{{ settings.color_scheme }}" style="--su-pt: {{ settings.padding_top }}px; --su-pb: {{ settings.padding_bottom }}px;">
<div class="su__card">
{% if settings.heading != "" %}<h2 class="su__h">{{ settings.heading }}</h2>{% endif %}
{% if settings.text != "" %}<p class="su__p">{{ settings.text }}</p>{% endif %}
<form class="su__form" data-storelib-form="subscribe">
<label class="su__label" for="{{ section.id }}-email">Email address</label>
<input class="su__input" id="{{ section.id }}-email" type="email" name="email" required autocomplete="email" placeholder="{{ settings.placeholder }}">
<button class="su__btn" type="submit">{{ settings.button_text }}</button>
<p class="su__done" data-storelib-success hidden>{{ settings.success_message }}</p>
</form>
</div>
</section>
</template>
<style>
.su { container-type: inline-size; background: var(--scheme-background); padding: var(--su-pt) var(--theme-padding-x, 24px) var(--su-pb); }
.su__card { max-width: 560px; margin: 0 auto; text-align: center; }
.su__h { margin: 0; color: var(--scheme-heading); font-size: var(--section-heading-size, 28px); line-height: var(--section-heading-line-height, 1.2); }
.su__p { margin: 12px 0 0; color: var(--scheme-text); font-size: var(--section-text-size, 16px); line-height: 1.6; }
.su__form { display: flex; flex-direction: column; gap: 10px; margin-top: 24px; }
.su__label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.su__input { min-width: 0; padding: 13px 16px; font: inherit; font-size: 16px; color: var(--scheme-input-text); background: var(--scheme-input); border: 1px solid var(--scheme-input-border); border-radius: {{ theme.radius.medium }}px; }
.su__btn { padding: 13px 22px; font: inherit; font-size: 15px; font-weight: 600; cursor: pointer; color: var(--scheme-button-primary-text); background: var(--scheme-button-primary); border: 1px solid var(--scheme-button-primary-border); border-radius: {{ theme.radius.medium }}px; }
.su__form[data-state="sending"] .su__btn { opacity: 0.6; }
.su__done { margin: 0; color: var(--scheme-heading); font-weight: 600; }
@container (min-width: 520px) {
.su__form { flex-direction: row; }
.su__input { flex: 1; }
}
</style>
<schema>
{
"type": "signup_card",
"name": "Sign-up card",
"category": "Marketing",
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "Get the next drop first" },
{ "type": "textarea", "id": "text", "label": "Text", "default": "One email when something new is ready. Nothing else." },
{ "type": "text", "id": "placeholder", "label": "Placeholder", "default": "you@example.com" },
{ "type": "text", "id": "button_text", "label": "Button", "default": "Notify me" },
{ "type": "text", "id": "success_message", "label": "After signing up", "default": "You're on the list." },
{ "type": "color_scheme", "id": "color_scheme", "label": "Colour scheme", "default": "light" },
{ "type": "range", "id": "padding_top", "label": "Top padding", "default": 64, "min": 0, "max": 200, "step": 4, "unit": "px" },
{ "type": "range", "id": "padding_bottom", "label": "Bottom padding", "default": 64, "min": 0, "max": 200, "step": 4, "unit": "px" }
],
"presets": [{ "name": "Sign-up card" }]
}
</schema>FAQ, one open at a time#
Blocks, a richtext answer printed with raw, and <details> elements sharing a name so opening one closes the others, all without script.
<template>
<section class="fq" data-scheme="{{ settings.color_scheme }}" style="--fq-pt: {{ settings.padding_top }}px; --fq-pb: {{ settings.padding_bottom }}px;">
<div class="fq__in">
{% if settings.heading != "" %}<h2 class="fq__h">{{ settings.heading }}</h2>{% endif %}
{% for block in blocks %}
<details class="fq__item" name="{{ section.id }}-faq" data-block-id="{{ block.id }}">
<summary class="fq__q">{{ block.settings.question }}</summary>
<div class="fq__a">{{ block.settings.answer | raw }}</div>
</details>
{% else %}
{% if page.in_editor %}<p class="fq__empty">Add a question from the panel.</p>{% endif %}
{% endfor %}
</div>
</section>
</template>
<style>
.fq { container-type: inline-size; background: var(--scheme-background); padding: var(--fq-pt) var(--theme-padding-x, 24px) var(--fq-pb); }
.fq__in { max-width: 760px; margin: 0 auto; }
.fq__h { margin: 0 0 24px; color: var(--scheme-heading); font-size: var(--section-heading-size, 32px); line-height: var(--section-heading-line-height, 1.15); }
.fq__item { border-bottom: 1px solid var(--scheme-border); }
.fq__q { display: flex; justify-content: space-between; gap: 16px; padding: 18px 0; cursor: pointer; list-style: none; color: var(--scheme-heading); font-weight: 600; }
.fq__q::-webkit-details-marker { display: none; }
.fq__q::after { content: "+"; flex: none; transition: transform 200ms ease; }
.fq__item[open] .fq__q::after { transform: rotate(45deg); }
.fq__a { padding: 0 0 18px; color: var(--scheme-text); line-height: 1.6; }
.fq__a > :first-child { margin-top: 0; }
.fq__empty { color: var(--scheme-text-muted); }
@media (prefers-reduced-motion: reduce) { .fq__q::after { transition: none; } }
</style>
<schema>
{
"type": "faq_exclusive",
"name": "FAQ, one open at a time",
"category": "Content",
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "Questions" },
{ "type": "color_scheme", "id": "color_scheme", "label": "Colour scheme", "default": "white" },
{ "type": "range", "id": "padding_top", "label": "Top padding", "default": 72, "min": 0, "max": 200, "step": 4, "unit": "px" },
{ "type": "range", "id": "padding_bottom", "label": "Bottom padding", "default": 72, "min": 0, "max": 200, "step": 4, "unit": "px" }
],
"blocks": [
{
"type": "question",
"name": "Question",
"limit": 30,
"settings": [
{ "type": "text", "id": "question", "label": "Question", "default": "How long does delivery take?" },
{ "type": "richtext", "id": "answer", "label": "Answer", "default": "<p>Most orders arrive within three working days.</p>" }
]
}
],
"max_blocks": 30,
"presets": [
{ "name": "FAQ", "blocks": [{ "type": "question" }, { "type": "question" }, { "type": "question" }] }
]
}
</schema>Logo strip#
Image blocks that fall back to a text name, a row that swipes on a phone with scroll snapping, and wraps into a centred row once there is room.
<template>
<section class="lg" data-scheme="{{ settings.color_scheme }}" style="--lg-pt: {{ settings.padding_top }}px; --lg-pb: {{ settings.padding_bottom }}px;">
<div class="lg__in">
{% if settings.heading != "" %}<p class="lg__h">{{ settings.heading }}</p>{% endif %}
<ul class="lg__row">
{% for block in blocks %}
<li class="lg__item" data-block-id="{{ block.id }}">
{% if block.settings.logo != "" %}
{% if block.settings.link != "" %}
<a href="{{ block.settings.link | url }}"><img class="lg__img" src="{{ block.settings.logo }}" alt="{{ block.settings.name }}" width="160" height="64" loading="lazy"></a>
{% else %}
<img class="lg__img" src="{{ block.settings.logo }}" alt="{{ block.settings.name }}" width="160" height="64" loading="lazy">
{% endif %}
{% else %}
<span class="lg__name">{{ block.settings.name }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</section>
</template>
<style>
.lg { container-type: inline-size; background: var(--scheme-background); padding: var(--lg-pt) 0 var(--lg-pb); }
.lg__in { max-width: var(--theme-page-width, 1280px); margin: 0 auto; }
.lg__h { margin: 0 0 20px; padding-inline: var(--theme-padding-x, 24px); text-align: center; color: var(--scheme-text-muted); font-size: 13px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; }
.lg__row {
display: flex; gap: 40px; margin: 0; padding: 0 var(--theme-padding-x, 24px); list-style: none;
overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scrollbar-width: none;
}
.lg__row::-webkit-scrollbar { display: none; }
.lg__item { flex: none; display: flex; align-items: center; scroll-snap-align: start; }
.lg__img { display: block; width: auto; height: 32px; object-fit: contain; opacity: 0.8; transition: opacity 200ms ease; }
.lg__img:hover { opacity: 1; }
.lg__name { color: var(--scheme-heading); font-weight: 600; white-space: nowrap; }
@container (min-width: 900px) { .lg__row { justify-content: center; flex-wrap: wrap; overflow: visible; } }
@media (prefers-reduced-motion: reduce) { .lg__img { transition: none; } }
</style>
<schema>
{
"type": "logo_strip",
"name": "Logo strip",
"category": "Social proof",
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "As seen in" },
{ "type": "color_scheme", "id": "color_scheme", "label": "Colour scheme", "default": "white" },
{ "type": "range", "id": "padding_top", "label": "Top padding", "default": 48, "min": 0, "max": 160, "step": 4, "unit": "px" },
{ "type": "range", "id": "padding_bottom", "label": "Bottom padding", "default": 48, "min": 0, "max": 160, "step": 4, "unit": "px" }
],
"blocks": [
{
"type": "logo",
"name": "Logo",
"limit": 16,
"settings": [
{ "type": "text", "id": "name", "label": "Name", "default": "Publication", "info": "Shown when there is no image, and read aloud by screen readers." },
{ "type": "image_picker", "id": "logo", "label": "Logo", "default": "" },
{ "type": "url", "id": "link", "label": "Link", "default": "" }
]
}
],
"max_blocks": 16,
"presets": [
{ "name": "Logo strip", "blocks": [{ "type": "logo" }, { "type": "logo" }, { "type": "logo" }, { "type": "logo" }] }
]
}
</schema>Video with text#
The embed capability and the embed filter: a creator pastes whatever link they have, and the frame only ever points at a known player. See Embeds.
<template>
<section class="vf" data-scheme="{{ settings.color_scheme }}" style="--vf-pt: {{ settings.padding_top }}px; --vf-pb: {{ settings.padding_bottom }}px;">
<div class="vf__in">
<div class="vf__copy">
{% if settings.heading != "" %}<h2 class="vf__h">{{ settings.heading }}</h2>{% endif %}
{% if settings.text != "" %}<p class="vf__p">{{ settings.text }}</p>{% endif %}
</div>
{% if settings.video_url != "" %}
<iframe class="vf__player" src="{{ settings.video_url | embed }}" title="{{ settings.video_title }}" loading="lazy" allow="autoplay; encrypted-media; picture-in-picture" allowfullscreen></iframe>
{% elsif page.in_editor %}
<p class="vf__empty">Paste a YouTube, Vimeo, Spotify or SoundCloud link in the panel.</p>
{% endif %}
</div>
</section>
</template>
<style>
.vf { container-type: inline-size; background: var(--scheme-background); padding: var(--vf-pt) var(--theme-padding-x, 24px) var(--vf-pb); }
.vf__in { display: grid; gap: 28px; max-width: var(--theme-page-width, 1280px); margin: 0 auto; }
.vf__h { margin: 0; color: var(--scheme-heading); font-size: var(--section-heading-size, 32px); line-height: var(--section-heading-line-height, 1.15); }
.vf__p { margin: 14px 0 0; color: var(--scheme-text); font-size: var(--section-text-size, 17px); line-height: 1.6; }
.vf__player { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: {{ theme.radius.medium }}px; background: var(--scheme-background-secondary); }
.vf__empty { margin: 0; padding: 48px 20px; text-align: center; color: var(--scheme-text-muted); border: 1px dashed var(--scheme-border); border-radius: {{ theme.radius.medium }}px; }
@container (min-width: 900px) { .vf__in { grid-template-columns: 2fr 3fr; align-items: center; gap: 48px; } }
</style>
<schema>
{
"type": "video_feature",
"name": "Video with text",
"category": "Media",
"capabilities": ["embed"],
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "See it in use" },
{ "type": "textarea", "id": "text", "label": "Text", "default": "Two minutes, start to finish." },
{ "type": "url", "id": "video_url", "label": "Video link", "default": "", "info": "A YouTube, Vimeo, Spotify or SoundCloud link, as you would copy it from the address bar." },
{ "type": "text", "id": "video_title", "label": "Video title", "default": "Product demo", "info": "Read aloud by screen readers." },
{ "type": "color_scheme", "id": "color_scheme", "label": "Colour scheme", "default": "white" },
{ "type": "range", "id": "padding_top", "label": "Top padding", "default": 72, "min": 0, "max": 200, "step": 4, "unit": "px" },
{ "type": "range", "id": "padding_bottom", "label": "Bottom padding", "default": 72, "min": 0, "max": 200, "step": 4, "unit": "px" }
],
"presets": [{ "name": "Video with text" }]
}
</schema>Product cards#
The creator's own published products through products, in the same { id, type, settings } shape as blocks. Prices arrive formatted, and a sale shows the old price struck through.
<template>
<section class="pc" data-scheme="{{ settings.color_scheme }}" style="--pc-pt: {{ settings.padding_top }}px; --pc-pb: {{ settings.padding_bottom }}px; --pc-cols: {{ settings.columns }};">
<div class="pc__in">
{% if settings.heading != "" %}<h2 class="pc__h">{{ settings.heading }}</h2>{% endif %}
<div class="pc__grid">
{% for block in products %}
<a class="pc__card" href="{{ block.settings.url | url }}">
<span class="pc__media">
{% if block.settings.image != "" %}<img class="pc__img" src="{{ block.settings.image }}" alt="{{ block.settings.title }}" width="600" height="600" loading="lazy">{% endif %}
{% if block.settings.tag != "" %}<span class="pc__tag">{{ block.settings.tag }}</span>{% endif %}
</span>
<span class="pc__title">{{ block.settings.title }}</span>
<span class="pc__price">
{{ block.settings.price }}
{% if block.settings.compare_price != "" %}<s class="pc__was">{{ block.settings.compare_price }}</s>{% endif %}
</span>
</a>
{% else %}
{% if page.in_editor %}<p class="pc__empty">Your published products appear here.</p>{% endif %}
{% endfor %}
</div>
</div>
</section>
</template>
<style>
.pc { container-type: inline-size; background: var(--scheme-background); padding: var(--pc-pt) var(--theme-padding-x, 24px) var(--pc-pb); }
.pc__in { max-width: var(--theme-page-width, 1280px); margin: 0 auto; }
.pc__h { margin: 0 0 24px; color: var(--scheme-heading); font-size: var(--section-heading-size, 28px); line-height: var(--section-heading-line-height, 1.2); }
.pc__grid { display: grid; gap: 20px 16px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pc__card { display: flex; flex-direction: column; gap: 6px; min-width: 0; color: inherit; text-decoration: none; }
.pc__media { position: relative; display: block; aspect-ratio: 1; overflow: hidden; border-radius: {{ theme.radius.medium }}px; background: var(--scheme-background-secondary); }
.pc__img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform 300ms ease; }
.pc__card:hover .pc__img { transform: scale(1.03); }
.pc__tag { position: absolute; top: 10px; left: 10px; padding: 3px 8px; font-size: 12px; font-weight: 600; color: var(--scheme-button-primary-text); background: var(--scheme-button-primary); border-radius: 999px; }
.pc__title { overflow-wrap: anywhere; color: var(--scheme-heading); font-weight: 600; }
.pc__price { color: var(--scheme-text); font-size: 14px; }
.pc__was { margin-left: 6px; color: var(--scheme-text-muted); }
.pc__empty { color: var(--scheme-text-muted); }
@container (min-width: 900px) { .pc__grid { grid-template-columns: repeat(var(--pc-cols), minmax(0, 1fr)); gap: 28px 20px; } }
@media (prefers-reduced-motion: reduce) { .pc__img { transition: none; } .pc__card:hover .pc__img { transform: none; } }
</style>
<schema>
{
"type": "product_cards",
"name": "Product cards",
"category": "Products",
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "New this week" },
{ "type": "range", "id": "columns", "label": "Columns on desktop", "default": 4, "min": 2, "max": 5, "step": 1 },
{ "type": "color_scheme", "id": "color_scheme", "label": "Colour scheme", "default": "white" },
{ "type": "range", "id": "padding_top", "label": "Top padding", "default": 64, "min": 0, "max": 200, "step": 4, "unit": "px" },
{ "type": "range", "id": "padding_bottom", "label": "Bottom padding", "default": 64, "min": 0, "max": 200, "step": 4, "unit": "px" }
],
"presets": [{ "name": "Product cards" }]
}
</schema>Tabs#
Blocks as tabs, switched with radio inputs and :checked, keyboard reachable, with the first tab open. The same pattern works for any "one of these at a time" layout.
<template>
<section class="tb" data-scheme="{{ settings.color_scheme }}" style="--tb-pt: {{ settings.padding_top }}px; --tb-pb: {{ settings.padding_bottom }}px;">
<div class="tb__in">
{% if settings.heading != "" %}<h2 class="tb__h">{{ settings.heading }}</h2>{% endif %}
<div class="tb__tabs">
{% for block in blocks %}
<input class="tb__radio" type="radio" name="{{ section.id }}-tab" id="{{ section.id }}-{{ block.id }}"{% if forloop.first %} checked{% endif %}>
<label class="tb__label" for="{{ section.id }}-{{ block.id }}" data-block-id="{{ block.id }}">{{ block.settings.title }}</label>
<div class="tb__panel">{{ block.settings.content | raw }}</div>
{% else %}
{% if page.in_editor %}<p class="tb__empty">Add a tab from the panel.</p>{% endif %}
{% endfor %}
</div>
</div>
</section>
</template>
<style>
.tb { container-type: inline-size; background: var(--scheme-background); padding: var(--tb-pt) var(--theme-padding-x, 24px) var(--tb-pb); }
.tb__in { max-width: 880px; margin: 0 auto; }
.tb__h { margin: 0 0 20px; color: var(--scheme-heading); font-size: var(--section-heading-size, 28px); line-height: var(--section-heading-line-height, 1.2); }
.tb__tabs { display: flex; flex-wrap: wrap; border-bottom: 1px solid var(--scheme-border); }
.tb__radio { position: absolute; opacity: 0; pointer-events: none; }
.tb__label { order: 0; padding: 10px 16px; margin-bottom: -1px; cursor: pointer; color: var(--scheme-text); border-bottom: 2px solid transparent; font-weight: 600; }
.tb__panel { order: 1; display: none; width: 100%; padding: 20px 0; color: var(--scheme-text); line-height: 1.6; }
.tb__panel > :first-child { margin-top: 0; }
.tb__radio:checked + .tb__label { color: var(--scheme-heading); border-color: var(--scheme-accent); }
.tb__radio:checked + .tb__label + .tb__panel { display: block; }
.tb__radio:focus-visible + .tb__label { outline: 2px solid var(--scheme-accent); outline-offset: 2px; }
.tb__empty { color: var(--scheme-text-muted); }
</style>
<schema>
{
"type": "tabs_panel",
"name": "Tabs",
"category": "Content",
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "Details" },
{ "type": "color_scheme", "id": "color_scheme", "label": "Colour scheme", "default": "white" },
{ "type": "range", "id": "padding_top", "label": "Top padding", "default": 64, "min": 0, "max": 200, "step": 4, "unit": "px" },
{ "type": "range", "id": "padding_bottom", "label": "Bottom padding", "default": 64, "min": 0, "max": 200, "step": 4, "unit": "px" }
],
"blocks": [
{
"type": "tab",
"name": "Tab",
"limit": 6,
"settings": [
{ "type": "text", "id": "title", "label": "Tab title", "default": "Materials" },
{ "type": "richtext", "id": "content", "label": "Content", "default": "<p>Organic cotton, dyed without chromium.</p>" }
]
}
],
"max_blocks": 6,
"presets": [
{ "name": "Tabs", "blocks": [{ "type": "tab" }, { "type": "tab" }, { "type": "tab" }] }
]
}
</schema>