Sections reference
A theme's layout is a list of sections. This is every section it can use,
what each one renders, and what it takes.
Two things decide whether a section is safe to put in a theme you hand to someone else:
- What it pulls from the store. Products, categories and feed posts come from whichever shop the theme is installed on. You never supply these.
- What it takes from you. Headings, labels and marketing copy live in the
section's
data. Leave one out and it falls back to a sensible default — which means your words are missing, not that the page breaks.
Every section below is safe. None of them will show another shop's copy.
The shape
{ "id": "unique-within-the-page", "type": "productGrid", "data": { "limit": 6 } }
id is any string, unique within the layout. type must be one of the
sections here — the server rejects anything else, so a theme can never carry a
section the storefront has no component for. data is optional.
Spacing, on any section
Every section accepts padding and margin in its data, each with any of
top, right, bottom, left in pixels:
{ "id": "grid", "type": "productGrid",
"data": { "margin": { "top": -40 }, "padding": { "top": 24, "bottom": 24 } } }
Margins take negative values down to -400, which is how you pull one
section up into the one above it — a band that should sit flush under a hero,
say. Padding is clamped at 0; a negative padding is not a thing CSS has.
Both cap at 400. Anything you leave out keeps the default rhythm, which is
56px between sections on a phone and 80px above it, and nothing above the
first section.
Set a margin and you set it for every screen width. The default rhythm is responsive; your number is not, so a large one that suits a desktop will be the same large one on a phone.
Sections that read the store
videoSlider
YouTube videos, one on screen at a time, with arrows and dots.
| Field | Default | Notes |
|---|---|---|
videos |
[] |
{ url, title } each; url is any YouTube link or video ID |
aspectRatio |
16:9 |
16:9, 4:3, 1:1 or 9:16 |
showTitles |
true |
Title of the current video under the slider |
rounded |
false |
|
fullWidth |
true |
Off stops at the page's maximum width |
videoCarousel
YouTube videos in a scrolling row, sized like the product carousel.
| Field | Default | Notes |
|---|---|---|
heading |
"" |
|
videos |
[] |
{ url, title, autoplay } each; autoplay plays that video muted and looped |
autoplayAll |
false |
Autoplays every video, overriding each one's own autoplay |
perView / perViewTablet / perViewMobile |
3 / 2 / 1 |
Videos across at each width |
gap |
18 |
Pixels, 0–48 |
aspectRatio |
16:9 |
16:9, 4:3, 1:1 or 9:16 |
arrows, dots, rounded, showTitles |
true |
|
fullWidth |
false |
Both show YouTube's thumbnail with a play button and only load the player when it is pressed, and only one video plays at a time.
productGrid
A grid of live products.
| Field | Default | Notes |
|---|---|---|
heading |
"" |
Shown above the grid; omit for a bare grid |
mode |
fixed |
fixed, pages or infinite — see below |
limit |
4 |
1–12. fixed only |
pageSize |
8 |
2–24. pages and infinite only |
category |
all | Filters to one category by name |
layoutSwitcher |
false |
Shows a 2 / 3 / 4 columns / list switcher above the products. The grid always opens on 4 columns |
listAlign |
left |
left, center or right — text alignment in the list layout |
fixed shows limit products and no more — the right choice for a teaser row
on a front page. pages shows the whole catalogue behind a numbered pager, and
infinite reveals another pageSize as the shopper scrolls, with a "Load more"
button for anyone whose browser does not run the observer.
Each page is one request. The first is fetched on the server, so the grid is
filled when the page arrives; the rest go to GET /products?limit=&offset=
as they are wanted. A block therefore costs one page of products however large
the catalogue behind it is, and category narrows that in SQL rather than
after the fact.
productListing
The whole catalogue listing: breadcrumb, title, filter rail and chips, sort, and the grid. This is what the New Arrivals page is made of, and it can go on any page.
| Field | Default |
|---|---|
heading |
NEW ARRIVALS |
intro |
the New Arrivals blurb |
crumb |
New Arrivals — empty drops the trail |
category |
everything |
limit |
0, meaning every product |
filters / sorting / count |
true |
The filters and the sort are kept in the address (?filter=…&sort=…), so a
filtered view is shareable and the first paint already has the right products.
A page can hold more than one listing, but they share those parameters.
listingHeader, listingFilterBar, listingFilters, listingResults
The same listing as four blocks, so a page can be built rather than configured. This is what New Arrivals is made of.
| Block | What it draws | Its fields |
|---|---|---|
listingHeader |
trail, title, blurb, count | heading, intro, crumb, category, count |
listingFilterBar |
quick chips, sort, grid/list | chips, group, limit, sorting, viewToggle, sticky |
listingFilters |
the sidebar of filter groups | title, groups (names; empty means all) |
listingResults |
the products | category, limit, columns |
They take no props from each other. Everything they agree on is in the
address — ?filter=…&sort=…&view=… — so they can be arranged freely: put
listingFilters and listingResults in a columns block to get a sidebar
beside the grid, drop a banner between the chips and the products, or use
listingResults on its own as a filtered grid. Two of them on one page share
the same parameters.
productListing above is all four in one section, for when a page just needs
a listing and not a layout.
productRails
Two rows of products, the shop's own, with a heading each.
| Field | Default |
|---|---|
firstTitle |
NEW DROPS |
secondTitle |
TRENDING NOW |
perRail |
4 (2–8) |
The first rail shows products 1–n, the second the n after them.
categoryGrid
The store's categories. Rendered as square cards, or as a scrolling row of
circles when the theme sets categoryShape: "circle".
| Field | Default | Notes |
|---|---|---|
heading |
SHOP BY CATEGORY |
Set "" for a bare grid |
columns |
4 |
2–5, desktop only |
categories |
all | An array of names, to pick and order them |
This section prints its own heading. Putting a heading block in front of
it gives the page two — one yours, one the default. Either drop the separate
block or set "heading": "" here.
ugcGrid
Customer photos. The shop chooses them, in the order it wants them.
photos is the list, each with url, user — whose picture it is — outfit,
the line along the bottom, and an optional href for that one picture's
button. With no photos it falls back to the store's feed posts, which is
what this section always did, and renders nothing when the feed is empty too.
| Field | Default | |
|---|---|---|
heading |
AS WORN BY YOU |
|
handle |
@mystore |
Clear it to drop the line under the heading |
invite |
to get featured |
Finishes "Tag @handle …" |
feedLabel / feedHref |
SEE THE FEED |
Without an address it flashes the app message, as before |
shopLabel / shopHref |
SHOP LOOK, /new-arrivals |
The button on each picture |
columns |
4 |
2 to 6 |
likeButton |
true |
The heart in the corner |
onMobile |
false |
Desktop only unless this is on |
Pictures are shown at 3:4, so they should be uploaded tall.
Sections you write
hero
The banner. Anything the block leaves empty falls through to the theme's
hero object, so a page can change the picture and keep the headline, and a
block with nothing set is exactly the theme.
Renders as a dark full-bleed image with the copy over it, or as copy beside
the photograph. The theme's heroLayout decides unless the block sets
layout to overlay (full width) or split — a store whose theme splits the
hero can still want one page where the picture runs the whole width.
images holds one or more pictures, each { url, mobileUrl }, and more than
one turns the background into a slideshow: they crossfade every interval
seconds, with dots to pick one. mobileUrl is the portrait crop a phone gets,
falling back to the landscape shot. The first picture is also written to url,
which is what a hero carried before it could hold several.
Nothing moves for a visitor who has asked for reduced motion.
featuredCollection
A full-width editorial panel: image on one side, copy on the other. Desktop only.
| Field | Default |
|---|---|
eyebrow |
FEATURED COLLECTION |
title |
line breaks with \n |
body |
— |
label / href |
SHOP COLLECTION / /new-arrivals |
image |
a placeholder |
stats |
["18 · pieces", "$38+ · from", "4.8 · rated"] |
Each stat is one string, value · label.
newsletter
The accent-coloured sign-up band.
| Field | Default |
|---|---|
title |
STAY IN\nTHE LOOP. |
body |
a line about new drops |
label |
SUBSCRIBE |
marquee
The scrolling strip under the hero. Desktop only.
| Field | Default |
|---|---|
items |
shipping, returns and discount messages |
heading
| Field | Notes |
|---|---|
eyebrow |
small, in the accent colour |
text |
the heading itself |
align |
left or center |
cta
A dark panel with a headline and one button: title, body, label, href.
richText
html — a paragraph or two of prose.
image
url or assetId, plus fullWidth.
cardCarousel
Cards you write yourself, in a row that scrolls — for everything the catalogue does not hold: services, guides, stockists, press.
cards is a list, each with url (the picture), title, eyebrow (the small
line above it), text, and href. Give a card label as well and the link is
drawn as words under the card; leave label empty and the whole card becomes
the link.
Around the row: eyebrow, heading and a linkLabel/linkHref pair beside
the heading. How it is drawn: aspect (landscape, square or portrait),
perView, perViewTablet, perViewMobile, gap, arrows, dots,
autoplay, interval and fullWidth.
postCardSlider
The same cards drawn as posts: posts rather than cards, and each one takes
meta — the date — beside its eyebrow above the title, with room for a
paragraph of text under it. Every other field is the same.
beforeAfter
Two pictures of the same thing, with a handle the reader drags across to compare them.
beforeUrl and afterUrl are the two shots, with beforeAlt/afterAlt and
beforeLabel/afterLabel (the words on the picture, BEFORE and AFTER by
default). Around them: eyebrow, heading and caption. How it is drawn:
aspect, orientation (horizontal or vertical), start — where the
handle sits, 5 to 95 — labels, rounded and fullWidth.
Nothing renders until both pictures are set, and both should be the same shot from the same place or the halves will not line up. The handle is a real slider: it drags, the picture can be clicked anywhere to move it, and the arrow keys work (Shift for bigger steps, Home and End for the ends).
ingredientShowcase
What a product is made of, and what each part is for.
items is the list, each with url (the picture), name, note — what that
part does — and amount, a short badge above the name: 2%, cold pressed,
Grade A. Leave amount empty and no badge is drawn, though the space is kept
so the names in a row still line up.
Around the list: eyebrow, heading, intro, and a linkLabel/linkHref
pair under it. imageUrl/imageAlt/imageShape are one picture of the
product itself.
layout is beside — the picture in its own column next to the parts — or
grid, which drops the picture and runs the parts across the page. A beside
block with no picture falls back to grid rather than leaving a hole. Also:
columns (2–4), shape (circle or square), background (none, cream,
sand or ink) and fullWidth.
imageSlider
Pictures the shopper swipes or clicks through. images is a list of
{ url, alt }; height in pixels (120–900), fit (cover or contain),
rounded, autoplay with interval in seconds, and fullWidth — on by
default here, unlike the other sections, because a slider is usually a banner.
productCarousel
Live products in a scrolling row. heading, category, limit (2–24), then
perView, perViewTablet, perViewMobile, gap, arrows, dots,
autoplay, interval, fullWidth, and the per-card extras showColors,
showSizes, showDescription, showStock.
youtube
url (any YouTube address), title, aspectRatio (16:9 or 4:3),
autoplay, fullWidth.
quill
html — the same as richText, written in the console's formatting editor.
A theme file should use richText; this exists for merchants.
spacer
height in pixels. Not padding for its own sake: the space between sections
is most of what separates a dense design from an airy one.
custom
A section a theme developer wrote: their own markup and CSS, with the shop's
text and products bound into it. name, template, css, fields and
values. The template language, the field types, what is stripped and the
limits are in Building a theme under "Sections you
write yourself".
Sections that hold sections
columns
Blocks side by side. columns is a list of { id, span, blocks } — span is
the share of the width that column takes, so 1 and 4 give a narrow sidebar
beside a wide body. Also gap in pixels and align (start, center,
end). A column can hold columns, three deep.
tabs
One panel at a time. tabs is a list of { id, label, blocks }, and
tabAlign (left, center, right) places the strip of buttons. Same
nesting limit as columns.
Mobile-only sections
mobileGreeting and mobileChips render below the desktop breakpoint and are
invisible above it. mobileChips takes chips (an array of labels).
mobileGreeting's copy is not yet configurable — the one section a theme
cannot fully speak for.
Writing a layout
The default front page, nine sections deep:
[
{ "id": "greeting", "type": "mobileGreeting", "data": {} },
{ "id": "hero", "type": "hero", "data": {} },
{ "id": "ticker", "type": "marquee", "data": {} },
{ "id": "chips", "type": "mobileChips", "data": {} },
{ "id": "rails", "type": "productRails", "data": {} },
{ "id": "cats", "type": "categoryGrid", "data": {} },
{ "id": "featured", "type": "featuredCollection", "data": {} },
{ "id": "ugc", "type": "ugcGrid", "data": {} },
{ "id": "news", "type": "newsletter", "data": {} }
]
A sparse one, the same building blocks arranged for a different kind of shop:
[
{ "id": "hero", "type": "hero", "data": {} },
{ "id": "s1", "type": "spacer", "data": { "height": 96 } },
{ "id": "h1", "type": "heading",
"data": { "eyebrow": "THE EDIT", "text": "QUIETLY EFFECTIVE.", "align": "center" } },
{ "id": "grid", "type": "productGrid", "data": { "limit": 6 } },
{ "id": "s2", "type": "spacer", "data": { "height": 96 } },
{ "id": "cats", "type": "categoryGrid", "data": {} },
{ "id": "s3", "type": "spacer", "data": { "height": 96 } },
{ "id": "news", "type": "newsletter",
"data": { "title": "JOIN THE\nRITUAL.", "label": "SIGN ME UP" } }
]
Same sections, different shop.
Before you ship it
- Install it into an empty store — no products, no uploaded images. That is how a stranger first sees it, and it catches a layout that only works against your own catalogue.
- Read every word on the page. Anything you did not set is a default, and a default written for a clothing shop reads badly on a pharmacy.
- Check the mobile surface.
featuredCollection,marqueeandugcGridare desktop only, so a layout built from those alone is an empty page on a phone. - Leave images unset unless you are shipping the theme inside the same deployment. An asset id only resolves where it was uploaded.