Building themes

Theme reference

Generated from the validator's own rules, so it cannot drift from what we accept. Point your editor at the JSON Schema and you get all of this as you type.

Nodes

What you can draw. A section's `render` is a list of these.

boxA container. Becomes a div, or any layout or landmark tag you name.
textA line of text, in the tag you name: a heading, a paragraph, a label.
richRichtext a merchant wrote, sanitised every time it is drawn.
imgA picture, drawn at the width its role says it will be drawn at: `role` is "main" (the default), "small" for a card or grid, "thumbnail" or "swatch". `mobileSrc` is a different picture for phones, a crop that works on a narrow screen. `priority: true` loads it at once, for the picture at the top of the page; in a repetition only the first one.
linkAn anchor around anything. `current` names classes added while the shopper is on its page (`match: "prefix"` counts the pages under it too), for a menu that marks where you are.
eachRepeats its children over a list, under a limit it does not choose.
ifDraws one set of children or another, on a condition.
slotRepeats its children once per repeatable block, with `block` in scope.
iconOne of the storefront's own icons, one the theme's `icons` define, or one the section's own `icons` define (same format; a section adds names, never replaces one).
moneyAn amount, formatted in the shop's display currency.
componentOne of the components below. The only interactive thing a theme can place.

Components

The interactive parts. You place and position them; you cannot write one. Those marked with slots take their layout from your theme.

product-cardThe theme's own card when its theme.json has a `productCard` tree (the product as `card`, what to show as `cardView`), otherwise the ready-made card: picture, sale label, title and price; `showPrice: false` hides the price, `showVendor: true` shows the maker. Built from nodes over the product; build your own from `item.url`, `item.image`, `item.price` and the parts above for a quick add, swatches or a quick view.
add-to-cartThe button, with a quantity field in front of it. Inside a product-form it adds whatever is chosen; elsewhere pass `variantId`. Pass `quantity: false` for a one-press add, `size: "small"` for a compact button beside a row, and `label` to change the words, or a `content` slot for words and an icon. Slots: content.
variant-pickerThe options, one group per axis (Size, Colour), with what cannot be bought greyed out. Inside a product-form. `style: "select"` makes each axis a dropdown; `option` narrows it to one axis. Buttons are radio inputs, so style the chosen one with `input:checked + span`. On a one-axis picker `detail: "price"` adds each value's price, and `detail: "pack"` also the price per unit and the saving on the first value, reading the count that starts each value ("12 tins"); `nameCls`, `priceCls`, `unitCls` and `saveCls` are your classes for the parts, `eachLabel` the word after the unit price.
custom-optionsThe product's fill-in fields: an engraving, a date, a file, gift wrap, whatever the merchant asked for, each with its extra price. Nothing on a product that asks for none. Any add-to-cart on the page reads it, so it need not sit inside a product-form.
variant-imageA variant's own picture as a small button, for a layout that lists variants as rows; pass `variantId`. Pressed, the product-gallery shows that picture; pressed again, or when another is pressed, the gallery goes back. Inside a product-form the picker does this on its own. Nothing when the variant has no picture.
search-boxThe ready-made search, the one the base theme uses: `layout` is "header" (an icon that opens a bar, the default), "panel" (a sheet under the header) or "page" (a plain field); `focus: true` puts the cursor in it. For a search of your own design, lay out the search parts instead: `search` around `search-field`, `search-terms` and `search-results`, usually in a `sheet`.
newsletter-formEmail capture, with the shop's own consent wording. `label` is the button's words; a `content` slot draws an icon in their place (the words still name it for screen readers). Slots: content.
cart-drawerThe ready-made basket: a sheet from the right, opened by the basket icon and by adding something. Its width is the cartWidth token in theme.json. Built from the pieces below; build your own from them for anything else.
paginationNext and previous, for a listing that runs past one page.
priceAn amount with its compare-at and its currency handled.
account-menuSign in, or the customer's own links when they are.
accordionRows that open. You lay out the title and the body; a merchant adds the rows. Slots: title (per block), body (per block).
tabsThe section's blocks in tabs, grouped by one of their settings: `by` names it ("topic" by default), and each different value is a tab, in the order the blocks first use it. `label` and `panel` are drawn per tab with `tab` in scope, its `name` and its `items` (the blocks' settings), so a panel can be an accordion with `items: tab.items` and `as: "block"`. `cls` for the row of tabs, `tabCls` each, `activeCls` the chosen one. With one topic only the panel is drawn. `cycle` (milliseconds) moves on to the next tab by itself, the timer starting again whenever the tab changes, so a line that fills in your CSS over the same time ends as the next tab opens; it never moves for a shopper who asked for less motion. Slots: label, panel.
image-carouselSlides the shopper can move through. You lay out one slide in `item` (once per block, or once per entry of `items` named by `as`); `perView` (3) fit at desktop width and `gap` sits between them. By default round arrows sit over the slides. Give `navCls` and the arrows move into a row of yours under the slides instead, with `prevCls` / `nextCls` for them (lay out your own icons in `prev` / `next`), `barCls` and `thumbCls` for a scroll bar whose thumb shows how far along the row is, and `counterCls` for "1 / 3". `trackCls` is your class on the scrolling row itself (to let it bleed off the page edge, say). Slots: item (per block), prev, next.
popup-panelSomething that opens when pressed. You lay out the button in `trigger` and the panel in `content`. `openOn: "hover"` opens it on hover as a menu dropdown does; `span: "full"` hangs it across the whole header, for a mega menu; `plain: true` drops its own border and padding so your content is the panel. `openCls` is added to the button while it is open (turn its icon into a close cross), and `openOnAdd: true` opens it when something is added to the basket, for a basket that drops from the header; with `span: "full"`, `follow: true` pins the open panel to the top of the window once the header has scrolled away. Slots: trigger, content.
shop-logoThe shop's own logo, at the size it was uploaded. Pass `src` (a theme setting of type image, say) to place a different one, a white version on a dark bar.
shop-menuThe ready-made menu: dropdowns on hover, or with `layout: "panel"` a button opening a full-width sheet with your `foot` slot under the columns. `trigger: "icon"` drops the word beside the hamburger and sets the button in the same square your other tools use. Built from popup-panel over `shop.menu`; build your own menus from the same, and a phone menu from a sheet with an each over `shop.menu`: a plain link for an item without children, a toggle with a toggle-button for one with (an accordion gives every row an arrow, and suits questions, not menus). Slots: foot.
cart-linkThe basket, and how many things are in it, as a link to the cart page.
product-formHolds which options are picked, for the parts inside it. Pass `product` (the page's `product`, or a list item). Lay out the page, a card or a quick view in its `content`. Nothing is chosen at first; `preselect` "first" or "last" opens on the first or last variant that can be bought (a pack picker on its best deal). Slots: content.
product-galleryThe product's pictures, one large with thumbnails under it and a full-screen view. Pass `images` (product.images) and `title`. It moves to the chosen variant's picture (`variant.image`) and back when the choice is cleared. For your own layout, an each over product.images with img nodes. As a slider instead: `thumbs: false` and `counter: false` leave those out, `dots: true` draws page dots under the picture (`dotsCls` for the row, `dotCls` each, `dotOnCls` the shown one), and `prevCls` / `nextCls` with `prev` / `next` slots put your own arrows over the picture, always shown. Slots: prev, next.
product-priceThe chosen variant's price, and what it was when it is on sale (an `s`). Inside a product-form.
stock-statusSold out, on backorder, or only a few left, for the chosen variant. Nothing when it is simply in stock.
sheetSomething that opens over the page: `side` is "right" (the default), "left", "bottom" or "center" for a popup. `openOnAdd: true` opens it when something is added to the basket; `label` names it for screen readers. You lay out the `trigger` and the `content`, and style the content's outer box for its width. `plain: true` leaves out the panel's own ground and shadow, so your content box is the whole panel: a card with round corners standing off the edge of the screen, say. Slots: trigger, content.
sheet-closeCloses the sheet it sits in. Draws the close icon, or your own `content`. Slots: content.
sort-menu"Sort by: Featured" and the orders a category can be put in (featured, best selling, newest, price either way), as a menu of links. On the category page only. `options` narrows it, a comma list such as "featured,bestselling,price-asc"; `label` replaces "Sort by"; `align` (end by default); `cls` for what is pressed, `currentCls` for the order in use, `listCls`, `linkCls` and `activeCls` for the menu. Any class you pass replaces the ready-made look for that part. `plain: true` when your `listCls` draws the whole card (its own background, border or shadow), so the panel around it draws none and the menu is one card, not two. For a layout of your own, `collection.sorts` holds the same links.
price-sliderThe category's price filter as a track with two handles, applied when a handle is let go. Reads `collection.price.slider`, which is null when the shop chose price bands under Settings, Attributes, so put it in an `if` on that and lay out `collection.price.ranges` in the `else`. Draws in the shop accent; recolour it with the CSS custom properties --t-slider-accent and --t-slider-rail, size the handles with --t-slider-thumb, and put `cls` on it for spacing.
countdownTime left until a moment, ticking every second: days, hours, minutes and seconds, each a box with its number and its word. `until` is the moment, usually `{ get: "settings.endsAt" }` from a setting of type datetime; nothing is drawn without one. `cls` for the whole, `unitCls` for each box, `numberCls` and `labelCls` for its two parts; `days: false` folds days into hours; `daysLabel`, `hoursLabel`, `minutesLabel`, `secondsLabel` replace the words (the shopper's language by default). Lay out what shows once it has passed in `ended`, or leave it empty to show nothing. Slots: ended.
wishlist-buttonThe heart: pressed, the product joins the shopper's wishlist; pressed again, it leaves. Pass `product` (the card's `card` when left out); `cls` for the button, `activeCls` added while the product is on the list (fill the heart), `label` and `activeLabel` for screen readers; lay out the icon in `content`. It may sit inside the card's link. A guest's list is kept in the browser, a signed-in customer's on their account. Draws nothing on a shop that has switched the wishlist off (`shop.wishlist` is false there, for your own `if` around a header link). Slots: content.
wishlist-countHow many products are on the wishlist, for a badge beside a header icon: a span with `cls`, nothing at zero unless `showZero: true`. Link the icon to /wishlist. Optional: any icon link to /wishlist or /compare already gets the engine's count badge (the basket's badge: recolour both with --t-count-bg, --t-count-fg and --t-count-radius on the header); place this only to draw the count yourself.
wishlist-itemsThe wishlist page's products, on the `wishlist` template: each drawn from the `item` slot (the product is `item`, the same shape as a list item) or your product card without one, in a list with `listClass`; `empty` is what shows with nothing saved. Slots: item, empty.
compare-buttonAdds the product to the comparison, or takes it off: the same props as wishlist-button, and your own button in `content`. Up to four products; a fifth lets the oldest go. Kept per browser. Draws nothing where the shop has switched comparison off (`shop.compare`). Slots: content.
compare-countHow many products are in the comparison, as wishlist-count. Link it to /compare.
compare-tableThe comparison, on the `compare` template: the products side by side (picture, name, a remove button), then price, rating and one row per option and attribute any of them has. `cls` for the table, `headCls` for a product's column head, `labelCls` for the row labels, `cellCls` for the values, `removeCls` for the remove buttons; `empty` shows with nothing to compare. Slots: empty.
buy-nowBuy it now, inside a product-form: what is chosen goes in the basket and the shopper goes straight to checkout, with the quantity from the form's add-to-cart field. Greyed until a variant is chosen, gone when it cannot be bought. `cls` for the button (the ready-made look without one), `label` to change the words.
scroll-topBack to the top: a button that appears once the shopper has scrolled `showAfter` pixels (600) and scrolls up smoothly. Without `cls` it is a small round button in the corner (`side` "right" or "left"); with `cls` it is yours to place (position: fixed in your CSS), `visibleCls` added while it shows so you can fade it in, and `content` for your own icon. The built-in Back to top section places one on any page. Slots: content.
revealA box that eases in as the shopper scrolls to it. Lay it out in `content`; `cls` is your class for the box and `waitCls` is added while it waits below the fold, so your CSS says where it starts (opacity 0, moved down) and the transition on `cls` plays when the class drops. `delay` (ms) staggers a row. Anything on screen when the page opens never waits, and nor does anything for a shopper who asked for less motion, or without JavaScript. Slots: content.
count-upA number that counts up from nothing as it comes into view: `value` such as "86%", "1,200+" or "4.9" (the words around the number stay), `cls` for the text, `duration` in ms (1600). The finished number is in the page from the start.
videoA silent film that loops, like a moving picture: `src` the file's web address (an MP4, usually from a url setting), `mobileSrc` another file for phones (a square or tall cut), `poster` a picture (an image setting) shown until the film plays, `label` for screen readers, `cls` for the video element (size it with width, height and object-fit: cover). The file loads only as the box nears the screen, and a shopper who asked for less motion keeps the poster. Nothing is drawn without a `src` that starts with https:// or /.
page-stateA box that knows what the header cannot: `homeCls` is added on the home page, `scrolledCls` once the window has scrolled past `after` pixels (8). `upCls` joins it while the last scroll went up: a header that hides going down (`.x-down .bar { transform: translateY(-100%) }`) and returns as soon as you scroll back up (`.x-down.x-up .bar { transform: none }`). Lay out what it holds in `content`, `cls` for the box. A bar that floats over the home page's first photo and settles into a full-width bar on scroll, and is full width on every other page, is these two classes in your CSS. It follows page changes, which a header otherwise never sees. Slots: content.
image-compareTwo pictures over each other with a handle to drag between them, a before and after. Lay out the pictures in `after` (it sets the size) and `before` (laid over it, cut at the handle); the labels that sit on them too. `cls` for the box (corners, ratio), `handleCls` for the line the handle is on, with your own grip in `handle`; `start` is where it begins (50), `label` names the slider for screen readers. It is a range input, so arrow keys move it. Slots: before, after, handle.
toggleSomething that opens and closes in place: a sidebar folded away, a group of filters, a list under its heading. Lay it out in `content`; `cls` is your class for its box and `openCls` is added while it is open, so your CSS says what open and closed look like. `open: true` (or an expression, a group holding the chosen filter say) starts it open. Slots: content.
toggle-buttonOpens and closes the toggle it sits in, from anywhere inside it. Lay out what is pressed in `content`; `cls` is your class for the button, `openCls` is added while the toggle is open (to turn a chevron, say), and `label` names it for screen readers when it is only an icon. Slots: content.
cart-iconThe basket icon with its count, for a sheet's trigger.
cart-quantityFewer and more for one line. Pass `line: { get: "line.id" }` and `quantity: { get: "line.quantity" }`.
cart-removeTakes one line out of the basket. Pass `line: { get: "line.id" }`; `label` overrides the wording, `style: "icon"` makes it the bin.
cart-clearEmpties the basket, after asking once.
discount-formThe discount code field and Apply, with why a code did not apply under it.
checkout-buttonGoes to this shop's checkout, whichever kind it runs. `label` overrides the wording; a `content` slot puts words and an icon in it. Slots: content.
search-formA plain search field that sends what is typed to a page as `q`. Pass `action` (a path on the shop, the blog's search say), `value`, `placeholder` and `label`; `buttonLabel` adds a submit button with those words after the field (none by default).
contact-formThe shop's contact form: name, email and message, and a field for each of the section's blocks of type `field` (settings label, type text, email, tel, textarea, select or radio, required, options one per line). Pass `buttonLabel` (or a `content` slot for words and an icon) and `successMessage`; `blockType` names a different block type. Messages reach the merchant's inbox. Slots: content.
product-tabsProducts in tabs, one tab per block of type `tab` (`blockType` names another): each block's `label`, and `source` "newest", "sale", "collection" (with `collection`, a handle) or "products" (with `p1` to `p8`, product handles). Lay out a tab in the `tab` slot and the chosen one in `active`, both with `block` in scope, and the products in `list`, where they are `items`. The first tab is drawn with the page; the others are fetched when a shopper reaches for them and kept. `limit` is products per tab (8, at most 24); `tabsClass` and `panelClass` are your classes for the row of tabs and the area under it. Slots: tab (per block), active (per block), list.
quick-addThe basket button on a product card. A product with nothing to choose goes straight into the basket; one with sizes, colours or a required fill-in field opens a popup with the product page's own choices, or with `mode` "redirect" goes to the product page. Pass `product` (the card's `card` when left out), `mode` ("popup", the default, or "redirect"), `label` (for screen readers when the button is an icon) and `cls`, your class for the button, which is `:disabled` while it adds and when sold out, and shows a tick for a moment once added; lay out what is pressed in `content`, an icon say. It can sit anywhere in the card, the card's link included. To let the merchant switch it off or choose the mode, declare theme settings `quickAdd` (boolean) and `quickAddMode` (select popup or redirect), wrap it in an `if` on `theme.quickAdd` and pass `mode` as `theme.quickAddMode`. Slots: content.
image-zoomMakes what is laid out in `content` (a picture, a tile) open the full-screen view of a set of pictures, the product gallery's own, with arrows, keys and Escape. Pass `images` (a list, `product.images` say), `image` (the picture this tile shows, the `each` alias, so the view opens at it), `title` (read to screen readers) and `cls`, your class for the pressable box. For a gallery of your own design, a mosaic or a stack, with the lightbox the ready-made gallery has. Slots: content.
reviewsReviews from verified buyers, when the shop collects them (it draws nothing otherwise). `source` is "product" (the product on the page), "shop", "all" or "auto" (the default: the product's on a product page, all elsewhere). `layout` is "list", "grid" (cards) or "slider", with numbered pages under a list or a grid once there are more than `perPage` (6, at most 24); `columns` (2 to 4) sets the grid's cards to a row on a wide screen; `summary: false` leaves out the average and the count per star, whose rows filter the list. Lay out one review in `item`, with `review` in scope: `rating`, `stars` (five true or false, for five icons), `title`, `body`, `author`, `initial`, `verified`, `dateText`, and `product` (`title`, `url`, `image`) when the list mixes products. `shop.labels.verified` says Verified buyer in the shop's language. `listClass` is your class for the list; the ready-made parts take their star colour from `--t-star`, which your CSS can set on any element above them. A product's average is also on the product and on every card as `rating` (`average`, `averageText` such as "4.0", `count`, `stars`), null without reviews. Slots: item.
reviews-badgeThe shop's reviews in one line: stars, the average such as "4.9" (in the shopper's own number format), how many in brackets, and words, Customer reviews by default. Draws nothing while the shop does not collect reviews or has none. `placement` is "inline" (where you put it; `align` "start", "center" or "end" in a row as wide as the page), "tab" (fixed to the middle of the screen's edge, the words running down it) or "corner" (a card fixed to a bottom corner); `side` "right" or "left" for those two. `href` makes it a link, the language kept; `label` replaces the words; `showLabel`, `showStars`, `showAverage`, `showCount` set to false leave a part out; `hideOnPhone: true` hides it on narrow screens; `colors: "custom"` with `background` and `color` paints it. `cls` is your class for the whole (the ready-made look is dropped, the placement is not). Lay out your own in `content` with `rating` in scope: `average`, `averageText`, `count`, `stars` (five true or false), `url`. Stars take `--t-star`. Slots: content.
box-builderThe box builder plugin's page: the boxes as cards (size, pack discount, "from" price), the flavours grouped by product type with - and + buttons and "Only N left", and the box drawn slot by slot beside them with its price, Add and Surprise me; a bar at the foot of a phone's screen instead. Draws nothing unless the plugin is installed with boxes and flavours. `heading` and `intro` (the shopper's language when empty), `round` the product types drawn as circles. Colours come from the theme; `--t-bx-box` and `--t-bx-inside` paint the box.
store-locatorThe Store locator plugin's stores (the shop's locations it put on the map): a map, a list or cards, with a search box, a Near me button and a chip per tag. `view` map-list|map|list|grid, `side` left|right|below|over (the list beside, under or floating over the map), `columns` 2 to 4 for the grid, `height` the map in px, `mapStyle` light|osm|soft|mono|dark|warm|france|humanitarian|topo (free tiles, no key), `clusters` (crowded pins become a count), `search`, `nearMe`, `filters`, `showImage`, `showHours`, `showContact`, `showDirections`, `width` contained|full, `heading` and `intro` (the shopper's language when empty), `pin` and `accent` (#hex). `cls`, `listCls`, `itemCls`, `mapCls` are your classes for the whole, the list, each store and the map. The `card` slot draws each store yourself (in the list and in the map's popup), with `store` in scope: `id`, `name`, `address` (one line), `lines` (the address as lines), `city`, `postalCode`, `country`, `phone`, `phoneHref`, `email`, `url`, `image`, `note`, `tags`, `hours` (seven `{ day, text }`, the shopper's language, Monday first), `pickup` (offered for collection at checkout), `directions` (a maps link). Open now, the distance and the active store are the component's. Colours from `--t-sl-pin`, `--t-sl-pin-fg`, `--t-sl-cluster`, `--t-sl-cluster-fg`, `--t-sl-accent`, `--t-sl-card-bg`, `--t-sl-card-fg`, `--t-sl-muted`, `--t-sl-line`, `--t-sl-radius`, `--t-sl-open`, `--t-sl-closed`, falling back to the shop's tokens. Draws nothing on a shop without the plugin. Slots: card.
store-locator-linkA link to the store locator's page, wherever the merchant moved it: a pin, and the word Stores in the shopper's language unless `showLabel` is false. `cls`, `iconCls`, `labelCls` are your classes; `label` replaces the word. Placing it anywhere in the header stops the plugin adding its own pin beside the account link. Nothing on a shop without the plugin.
hotspotsProducts pinned to pictures, from the section's `slide` blocks (image, alt) and `spot` blocks (product, slide, x, y, marker dot|plus|square, line, card above|below|left|right|custom with cardX/cardY, note). `layout` half (the `content` slot beside the picture, `side` right|left) or full (the content above it), `ratio` the picture's shape ("4 / 5"), `cards` open|hover, `thumbs` the other pictures as thumbnails with a counter and arrows, `priority: true` loads the first picture at once (the section at the top of the page). The ready-made card is the product's picture, name, note (or brand), price and a + that adds it or opens its choices; a `card` slot draws your own, with the product as `card` and the pin as `spot`. Colours from `--t-hs-marker`, `--t-hs-marker-fg`, `--t-hs-line`, `--t-hs-card-bg`, `--t-hs-card-fg`, `--t-hs-card-muted`, `--t-hs-card-radius`, `--t-hs-card-shadow`, `--t-hs-add-bg`, `--t-hs-add-fg`, `--t-hs-add-radius` (the + and the arrows, round by default), `--t-hs-thumb-active`, `--t-hs-stage` (the picture's ground, transparent to show the section's own behind a cut-out) `--t-hs-fit` (cover, or contain for a cut-out), `--t-hs-pad` and `--t-hs-gap` (the section's space above and below, and between words and picture), falling back to the shop's tokens. Slots: content, card.
age-gateThe Age check plugin's screen: a cover over the whole page and a panel asking the visitor's age, until they answer (remembered in the `wz_age` cookie). Place it once, in a footer section. Props: `cls` (the panel), `yesCls`, `noCls` (the two buttons), `fieldCls` (the date of birth input, when the plugin asks for one), `msgCls` (the line shown after No), `yes`, `no` (button words, else the shopper's language), `heading`, `text`, `logo` (used when there is no `content` slot), `layout` card|split|full, `image` (behind the card, beside the words, or across the screen), `dim` (0 to 95, how dark the cover is), `blur`, `align` center|start. The `content` slot replaces the logo, heading and text above the buttons. Colours from `--t-ag-backdrop`, `--t-ag-panel`, `--t-ag-ink`, `--t-ag-btn-bg`, `--t-ag-btn-fg`, `--t-ag-radius`, falling back to the shop's tokens. Nothing on a shop without the plugin, and it only renders in the footer's or header's sections. Slots: content.
popupThe Popups plugin's popup, over the page when its rules say so; place it in a footer section (once per popup). Words and parts: `heading`, `eyebrow`, `text` (or your own in the `content` slot, which replaces those three), `image` with `imageAlt`, `imageLayout` top|left|right|background|only and `imageLink`, `newsletter` (true for the sign-up form, `formButton`, `formPlaceholder`), `code` (a promo code with a copy button), `button` and `buttonLink`, `decline` (a link that closes it). When: `trigger` entry|delay|scroll|exit with `delay` (seconds) or `scroll` (percent), `frequency` always|session|days|once with `days`, `startsAt` and `endsAt` (ISO instants), `autoClose` (seconds, 0 never), `pages` all|home|products|collections|cart|paths with `paths` (one per line, * at the end for a prefix), `devices` all|desktop|mobile, `name` (changing it shows it again to those who closed it). Look: `position` center|top|bottom|top-left|top-right|bottom-left|bottom-right|left|right|bar-top|bar-bottom|full, `width` small|medium|large, `animIn` and `animOut` fade|up|down|left|right|zoom|none, `speed` (ms), `backdrop` (false leaves the page usable), `dim`, `blur`, `closeOutside`, `align` center|start, `radius`, `panel`, `ink`, `buttonColor`, `buttonInk`, `backdropColor` (#hex colours), `cls` (your panel class), `btnCls`, `closeCls`. Colours from `--t-pp-panel`, `--t-pp-ink`, `--t-pp-btn-bg`, `--t-pp-btn-fg`, `--t-pp-backdrop`, `--t-pp-radius`, `--t-pp-shadow`, falling back to the shop's tokens. Draws nothing on a shop without the plugin. Slots: content.
searchSearch you lay out yourself. Wrap the parts in its `content` slot, anywhere: in a `sheet` (side "top" drops it from the top with the page dimmed), a panel or a page. It holds what is typed and what it found; a search closes the sheet it is in when it goes somewhere. Slots: content.
search-fieldThe search box, inside `search`. `cls` is your class for it (the field takes its colours), `icon` is "start", "end" or "none" for the magnifier, `focus: false` keeps the cursor out of it when it appears.
search-termsWords to search by, inside `search`: before typing the shopper's recent searches and the shop's popular ones; while typing the categories and searches that match, or the names of what matched. `cls` for the lists, `headingClass`, `itemClass`, `limit` (8).
search-resultsWhat a search found, inside `search`: each product drawn from the `item` slot (the product is `item`, a whole product with its variants and rating, so `add-to-cart` and `quick-add` work on it: `url`, `image`, `title`, `description`, `price`...), or your product card without one. `limit` is how many (4, at most 12), `listClass` your class for the list, `moreClass` for the link to all results, `emptyClass` for the searching and nothing-found line, and `idle` a slot shown before anything is typed. Slots: item, idle.
login-formSigning in with a six-digit code sent by email. On the login template.
register-formCreating an account: name, email, and the marketing question. On the register template.
google-buttonContinue with Google, when the shop offers it (draws nothing otherwise). `label` overrides the wording.
currency-switcherThe currencies this shop actually sells in, as a ready-made control. For your own, place currency-button over `shop.currencies`.
language-linkThis page in another language. Pass `code` (a `shop.languages` item's code) and lay out what the shopper presses in `content`: the name, a flag. Slots: content.
currency-buttonShows the shop's prices in another currency. Pass `code` (from `shop.currencies`) and lay out what is pressed in `content`. Slots: content.
noticeA message screen readers announce: `tone` is "info" (the default) or "alert" for something that went wrong. Lay out the message in `content`. Slots: content.
language-switcherThe languages this shop is published in, as a ready-made control. For your own, place language-link over `shop.languages`.
social-linksThe shop's own accounts, as icons. Set `--t-social-size` on a parent to size them (20px by default).
blog-post-listThe ready-made list of posts, with its paging, built from nodes over `plugin.list`. `columns` is posts to a row, "1" (the default, each post across the width) to "3". On the blog's list template only.
blog-sidebarThe blog's sidebar: search, categories, recent posts, as the merchant set it up.
blog-post-bodyThe post itself: breadcrumbs, title, meta line, cover, body, gallery, tags and share links. On the post template only.
blog-post-navPrevious and next post.
blog-related-postsPosts related to this one. `heading` overrides the wording.
blog-related-productsProducts the post is about. `heading` overrides the wording.

Header and footer

In the header and footer the merchant's menu, languages and currencies are in scope under `shop`. Lay a menu out with ordinary nodes and popup-panel (on hover, across the header, or plain so your box is the panel); a phone menu with a sheet holding an each over shop.menu, a plain link for an item without children and a toggle with a toggle-button for one with (not an accordion, which gives every row an arrow); and a language or currency choice with language-link and currency-button. shop-menu is the ready-made menu, built from exactly these.

shop.menuThe main menu the merchant built, for an `each`. Every item has label, url (empty for a heading), hasChildren, hasPanel, children (the same shape, up to three levels down when an entry shows its subcategories), mega (true when the merchant asked for a mega menu: draw each child as a column) and panel (image, link, html) when the merchant added a promo.
shop.languagesThe languages the shop publishes: code, name (in its own language), current. For language-link.
shop.currenciesThe currencies the shop sells in: code, current. For currency-button.
shop.labels.menu, shop.labels.seeAllThe words a menu needs, in the shopper's language.

Products

On the product page `product` is in scope, and every product list a section asks for (newest, a category, related) holds the same shape. Lay a page or a card out with ordinary nodes; wrap what changes with the chosen size or colour in a product-form, with variant-picker, product-price, stock-status and add-to-cart inside it. product-card is the ready-made card, built from nodes over these fields.

title, handle, vendorAs the merchant entered them.
urlThe product's address, for a link.
imageThe first picture, for an img. `images` is all of them, for an each or a carousel.
price, compareAtThe lowest price, and what it was; draw them with `money`.
onSale, soldOutTrue or false, for an `if`: a sale label, a sold-out badge.
discountPercent, savingHow much is off when on sale: a whole percent for a "-25%" badge (rounded down, 0 when not on sale; join it with "%") and the amount saved, for a money node.
optionsThe option axes, each a name and its values: Size S M L. What variant-picker draws. An axis the shop draws as colours also has `colours`, each a value and its colour, for a row of dots on a card (`style` background-color from `c.colour`).
variants[].imageThe combination's own picture, when the merchant gave it one; the gallery shows it while that variant is chosen.
specsOn the product page: the attributes the shop lists there, each a code, label, values and filterable. Empty when the shop lists none.
skuThe product's code: its one variant's, or the start every variant's code shares (LX-KNIT for LX-KNIT-S and LX-KNIT-M); empty when a variant has none.
variantsEvery variant: id, title, options, price, sku, available, stock (sold_out, backorder, low, in_stock) and, on the product page, lowText ("Only 3 left" in the shopper's language).
single, multi, relatedOn the product page: the lone variant when there is only one, whether there are several, and the related products as list items, with their variants and rating.
descriptionHtml, shortDescriptionHtmlThe descriptions, for a `rich` node. On the product page.
ratingThe product's reviews: average, averageText ("4.0"), count and stars (five true or false); null with no reviews yet or reviews off, so `if` on `product.rating` shows a Reviews tab or heading only once there are some. On the product page and on every card.
shop.wishlist, shop.compare, shop.reviewsWhether the shop offers a wishlist, the comparison, and collects reviews, for an `if` around a link, a button or a reviews fold.
shop.labelsThe storefront's words in the shopper's language, everywhere: addToCart, soldOut, sale, backordered, options, quantity, viewProduct, noImage, close, and for a page not found notFound, notFoundBody and backToShop.

The blog

On the blog's templates the plugin puts its page in scope as `plugin`: the list or the post, the sidebar, the merchant's display choices and the words. Lay the blog out over it with ordinary nodes and search-form for its search. The blog-* components are its ready-made pieces, built from exactly this.

plugin.list.postsOn the list page: the posts, for an each. Each has title, url, cover, excerpt, categories (name, url, sep), meta (text, url, sep: the author, date and reading time the merchant chose to show) and hasMeta, hasCategories.
plugin.list.has, plugin.list.emptyWhether there are posts, and what to say when a search or a category has none.
plugin.list.paged, plugin.list.newerUrl, plugin.list.olderUrl, plugin.list.pageTextPaging: whether there is more than one page, the neighbouring pages, and "Page 2 of 5".
plugin.postOn the post page: the same fields as a list post, and html (the body, for a rich node), gallery, tags, share (name, url), trail (the breadcrumbs), previous and next (title, url), related (posts) and products (product-list items, for product-card or your own card), isDraft.
plugin.sidebarsearch (whether to offer it), searchUrl and query for search-form; recent (title, url); categories (name, url, count, indent); tags (name, url); any.
plugin.settings, plugin.labelsThe merchant's display choices (showBreadcrumbs, showPrevNext, showShareButtons, readMoreText) and the blog's words in the shopper's language.

Signing in

The sign-in and create-account pages are the `login` and `register` templates, with `account` in scope. login-form, register-form and google-button are the shop's own, because they handle who somebody is; lay out everything around them. The pages' own sections are built from exactly these.

account.google, account.googleErrorWhether the shop offers Google sign-in, and why the last attempt did not finish, to show when not empty.
account.loginUrl, account.registerUrlThe two pages, each keeping where the shopper was going.
account.labelsThe pages' words in the shopper's language: signIn, whySignIn, codeIntro, firstTime, createAccount, registerIntro, haveAccount, continueWithGoogle, or.

Category page

The category page is the `collection` template, with the category in scope as `collection`. The built-in collection-products section draws the grid, the filters and the paging; lay out the heading, the trail and the links to other categories around it.

collection.title, collection.description, collection.imageUrl, collection.urlThe category as the merchant set it up, and its address.
collection.crumbsThe categories above it, top first (handle, title, url), for a trail back.
collection.parentThe category it sits under (handle, title, url), or null at the top.
collection.childrenThe categories directly under it (handle, title, url), for links under the heading.
collection.siblingsThe categories beside it under the same parent, itself included (handle, title, url); empty at the top. With a link's `current`, a row of them marks the one being viewed.
collection.products, collection.pagingThis page of its products, for an each with product-card, and the paging (page, pages, total). The built-in collection-products section draws them with the filters; a section of your own that lays out the grid sets how many to a page with a `perPage` setting.
collection.treeEvery top-level category (handle, title, url, current, open: this one or one under it is being viewed) with its children (handle, title, url, current), for a category list in a sidebar.
collection.facetsThe filters the shop offers here: each has code, label, open (a value is chosen) and values, each with label, count, selected, swatch (a colour, or empty) and url, the address with that value turned on or off.
collection.filtered, collection.clearUrlWhether any filter or a price is on, and the address with none.
collection.sorts, collection.sortLabelThe orders a shopper can choose (value, label, url, selected): the category's own order first, then best selling (units sold on paid orders in the last 90 days), newest, and price either way; and the name of the one in use. The `sort-menu` component draws them ready-made.
collection.priceThe price filter: selected, clearUrl, and ranges, round bands worked out from the prices on the shelf, each with min and max (in the shopper's currency, for money nodes; 0 at an open end), from and open (true at the ends), label ("€50 - €100", ready-made), count, selected and url. Empty ranges when the shelf is all one price.
collection.pageLinks, collection.prevUrl, collection.nextUrlThe pages to link to, each n, url and current, or gap for the dots between; and the neighbours, empty at either end. Filters and the order carry across.
collection.showing, collection.labels"Showing 1-24 of 60 products", and the words around it in the shopper's language: filter, clear, sortBy, price, previous, next, and empty for a category with nothing in it.

Search results

The search results page is the `search` template, with the query and its results in scope as `search`. Place search-box with layout page for the field, product-card or your own card over search.results, and the categories for when nothing matched. The page's own section is built from exactly these. search-box is the ready-made field and dropdown; for a dropdown of your own, build it from search, search-field, search-terms and search-results, whose item slot draws each suggestion as a whole product, variants included.

search.query, search.hasQueryWhat was searched for, and whether anything was.
search.results, search.foundThe products that matched, for an each (the same shape as any product list, so product-card or your own card), and whether there were any.
search.heading, search.countText"Results for …" or "Search", and "12 products matching …" or "Nothing matched …", in the shopper's language.
search.collectionsThe shop's categories (title, url), somewhere to go when nothing matched.
search.prompt, search.noMatchHelp, search.browseHeadingThe sentences around them: before a search, after one that found nothing, and over the categories.

Order confirmation

The thank-you page after paying is the `success` template, with the shop's own header and footer and the order this browser just placed in scope as `order`. Every amount is already text in the currency the order was charged in, so lay it out with plain text nodes. The page's own section is built from exactly these.

order.number, order.heading, order.confirmedThe order's name (#1001 or the shop's own prefix), "Thank you", and "Your order #1001 is confirmed."
order.paid, order.status, order.method, order.statusTextWhether it is paid, "Paid" or "Awaiting payment", how it was paid, and what happens next.
order.instructions, order.referenceWhat an unpaid order still needs from the shopper (keep its line breaks), and a purchase order's reference.
order.itemsWhat was bought: title, variant, quantity, each ("2 × €6.50"), total, image.
order.rows, order.totalThe lines above the total (label, value): subtotal, each promotion, shipping, tax, tip. Then the total.
order.delivery, order.address, order.hasAddressHow it is coming, the address as lines, and whether there is one.
order.email, order.phone, order.shopUrlWhere the receipt went, and the way back to the shop.
order.labelswhatYouOrdered, total, deliveredTo, contact, continueShopping, in the shopper's language.

The basket

In a header or footer, and on the cart page (the `cart` template), the visitor's basket is in scope as `cart`. Lay it out with ordinary nodes and place sheet, cart-icon, cart-quantity, cart-remove, cart-clear, discount-form and checkout-button for what it does: a drawer from either side, a tray from the bottom, a popup in the middle, or a whole cart page. cart-drawer and the cart page's own section are the ready-made ones, built from exactly these parts.

cart.itemCountHow many things are in the basket, counting quantities.
cart.emptyTrue when there is nothing in it. For an `if`.
cart.subtotalThe lines added up, before shipping; draw it with a `money` node.
cart.linesThe lines, for an `each`. Each has id, title, variant (or empty), sku (or empty), image, url, quantity, unitPrice and total.
cart.discount, cart.discounts, cart.hasDiscountWhat was taken off: the total, and one entry per promotion (name, amount, free) so a shopper can check each.
cart.shipping, cart.freeShipping, cart.needsDeliveryDelivery, and whether there is anything to deliver at all.
cart.tax, cart.hasTax, cart.taxIncluded, cart.totalTax (included in prices or on top), whether there is any (hasTax is false at zero, so a shop that charges none shows no tax line), and what it all comes to.
cart.code, cart.codeErrorThe discount code entered, and why it did not apply.
cart.conversionNote, cart.demoNote, cart.stockNotice, cart.paymentFailed, cart.minimumNoticeSentences to show when they are not empty: prices shown in another currency, a demo shop, stock that changed, a payment that did not start, a basket below the shop's minimum order (the checkout-button already says this one and holds).
cart.freeShippingBarThe free delivery plugin's bar, { text, percent }, or null when it is not installed or no rule would make this basket free. The checkout-button draws it above itself; colour its fill with --t-fsb-fill and square its ends with --t-fsb-radius (round by default).
cart.labelsThe cart's words in the shopper's language: title, empty, startShopping, summary, product, quantity, each, subtotal, discount, discountCode, apply, shipping, free, freeOffer, tax, total, checkout, paymentFailed, viewCart, emptyCart, remove, close.

Data

A section declares what it needs and the page fetches it before drawing anything, so two sections asking for the same list cost one query. A theme cannot write a query of its own.

newest-productsThe most recently added products in the shop.
collection-productsProducts in a category, or the newest if none is chosen.
collectionsThe shop's categories.
product-variantsThe variants of the product on this page.
product-imagesThe pictures of the product on this page.
related-productsProducts that go with the one on this page.
metaobjectsEntries of a custom content type your theme defines.
cart-linesEmpty on purpose: pages are drawn once for every visitor. The basket is in scope as `cart` in the header and footer, which are drawn per visitor.
menuA named menu the merchant built.
blog-postsThe blog's public posts, newest first; `category`, `tag` (handles) and `featured` narrow them. Each has title, url, cover, excerpt, author, readingMinutes, date (as the shopper reads dates) and category (name, url, or empty). Empty without the blog plugin.
blog-categoriesThe blog's categories, as name, handle and url.
instagram-postsThe shop's Instagram posts, newest first, from the Instagram feed plugin; `account` (a username) narrows to one account, `count` takes how many (both usually from settings). Each has url (the post on Instagram), image, caption, type (image, video, carousel), video and carousel (true or false), likes and comments (numbers, null when hidden), likesText and commentsText (short, as the shopper reads numbers, 1.2K), date, username and accountUrl. Empty without the plugin or a connected account. Lay them out with each for a grid or image-carousel for a slider.
productOne product, the one `product` names (a handle, usually `{ get: "settings.product" }` from a setting of type product): bound as that product, the same shape as a list item, or null when none is chosen. For a featured product or a deal of the day; place it in a product-form to sell it there.

Design tokens

Your theme.json sets the defaults; the merchant can change any of them. Read them in CSS as the custom property beside each one.

colorBgPage background. CSS: var(--shop-bg). Default: #ffffff
colorFgText. CSS: var(--shop-fg). Default: #0f1115
colorAccentAccent. CSS: var(--shop-accent). Default: #0b0f19
colorMutedMuted text. CSS: var(--shop-muted). Default: #6b7280
fontHeadingHeading font. CSS: var(--shop-font-heading). Default: "Inter", system-ui, sans-serif
fontBodyBody font. CSS: var(--shop-font-body). Default: "Inter", system-ui, sans-serif
fontAccentAccent font. CSS: var(--shop-font-accent). Default: "Inter", system-ui, sans-serif
fontSizeBodyBody text size. CSS: var(--shop-font-size-body). Default: 16px
fontSizeHeadingLargest heading size. CSS: var(--shop-font-size-heading). Default: 60px
radiusCorner radius. CSS: var(--shop-radius). Default: 0
maxWidthContent width. CSS: var(--shop-max-width). Default: 1200px
cartWidthCart drawer width. CSS: var(--shop-cart-width). Default: 30rem
insetLeftRoom for a side menu. CSS: var(--shop-inset-left). Default: 0px
controlRadiusButton and field radius. CSS: var(--shop-control-radius). Default: 0
btnBgButton background. CSS: var(--shop-btn-bg). Default: #000
btnFgButton text. CSS: var(--shop-btn-fg). Default: #fff
btnBorderButton border. CSS: var(--shop-btn-border). Default: #000
imageMainMain image, desktop. CSS: var(--shop-img-main). Default: 50vw
imageMainMobileMain image, mobile. CSS: var(--shop-img-main-mobile). Default: 100vw
imageSmallGrid image, desktop. CSS: var(--shop-img-small). Default: 25vw
imageSmallMobileGrid image, mobile. CSS: var(--shop-img-small-mobile). Default: 50vw
imageThumbnailThumbnail, desktop. CSS: var(--shop-img-thumbnail). Default: 96px
imageThumbnailMobileThumbnail, mobile. CSS: var(--shop-img-thumbnail-mobile). Default: 72px
lightboxBgPicture view background. CSS: var(--shop-lightbox-bg). Default: #FFFFFF
lightboxPanelPicture view panel. CSS: var(--shop-lightbox-panel). Default: #F0F0F0
lightboxButtonBgPicture view buttons. CSS: var(--shop-lightbox-btn-bg). Default: #FFFFFF
lightboxButtonFgPicture view button icons. CSS: var(--shop-lightbox-btn-fg). Default: #000000
imageSwatchSwatch, desktop. CSS: var(--shop-img-swatch). Default: 48px
imageSwatchMobileSwatch, mobile. CSS: var(--shop-img-swatch-mobile). Default: 40px
imageRatioCardProduct picture on cards. CSS: var(--shop-img-ratio-card). Default: 1 / 1
imageRatioProductProduct picture on the product page. CSS: var(--shop-img-ratio-product). Default: 1 / 1
checkoutSummarySideOrder summary on the. CSS: var(--shop-checkout-direction). Default: row
checkoutSummaryBgSummary background. CSS: var(--shop-checkout-summary-bg). Default: #FFFFFF
checkoutSummaryFgSummary text. CSS: var(--shop-checkout-summary-fg). Default: #000000
checkoutFontHeadingCheckout heading font. CSS: var(--shop-checkout-font-heading). Default: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif
checkoutHeadingSizeCheckout heading size. CSS: var(--shop-checkout-heading-size). Default: 24px
checkoutBgForm background. CSS: var(--shop-checkout-bg). Default: #FFFFFF
checkoutFgForm text. CSS: var(--shop-checkout-fg). Default: #000000
checkoutAccentButtons and chosen options. CSS: var(--shop-checkout-accent). Default: #000000
checkoutAccentFgButton text. CSS: var(--shop-checkout-accent-fg). Default: #FFFFFF
checkoutRadiusCorners of fields and buttons. CSS: var(--shop-checkout-radius). Default: 0px

Expression roots

Where a `get` path may start. Which of these are in scope depends on where you are: `block` inside a slot, `item` (or whatever you named it) inside an each.

settings · theme · block · shop · page · product · collection · cart · search · account · order · plugin · customer · data

Tags

What a box and a text node may become.

box

div · section · header · footer · nav · main · aside · article · ul · ol · li · figure · figcaption

text

p · h1 · h2 · h3 · h4 · h5 · h6 · span · strong · em · small · blockquote · label

Icons

The storefront's own set. An icon node names one of these, a name from the theme's icons, or a name from the section's own icons (same format as theme.json; a section's icons add names and never replace one the storefront or the theme already draws).

search · account · bag · bin · menu · close · chevron · clock · trend · folder · check · star · play · delivery · guarantee · returns · instagram · facebook · x · tiktok · youtube · pinterest · linkedin

Limits

Ceilings, all of them boring. A bundle is read on every request, so its size is a speed budget as much as a safety one.

sectionsPerBundle60
fieldsPerSection60
blocksPerSection12
fieldsPerBlock20
templateSections40
metaobjectTypes40
fieldsPerMetaobject30
cssBytes200,000
sectionCssBytes40,000
bundleBytes2,000,000
dataRequestsPerSection6
customSections30
customSectionBytes60,000