THEMES

Sections reference

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, marquee and ugcGrid are 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.
Sections reference — Docs