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

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, 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