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
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 |
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.
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
The store's feed posts. Renders nothing when the shop has none.
| Field | Default |
|---|---|
heading |
AS WORN BY YOU |
handle |
@mystore |
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.
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.
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.