THEMES

Building a theme

A theme is a single JSON file. It controls how a storefront looks — palette, display font, header menu, home banner and footer. It never contains products, categories, orders or settings, which is why one file works in any store.

Anyone can write one by hand. Nothing needs to be compiled.


The file

{
  "format": "my-store.theme",
  "version": 1,
  "theme": {
    "name": "Midnight",
    "fontFamily": "Space Grotesk",
    "colors": { "...": "9 tokens" },
    "style": { "...": "shape, spacing and construction" },
    "layout": [ { "id": "hero", "type": "hero", "data": {} } ],
    "menu": [ { "label": "New Arrivals", "href": "/new-arrivals" } ],
    "hero": { "...": "banner image and copy" },
    "footer": { "...": "columns and small print" }
  }
}

format must be exactly my-store.theme — it is what stops an unrelated JSON file being imported. version is optional today; bump it only when a future change makes old files unreadable.

Import rejects anything that fails validation, field by field, so a mistake gives you a message like theme.colors.ink must be a hexadecimal color rather than a broken shop.


Colours

All nine are required, all hex (#RRGGBB or #RGB).

Token What it paints Weight
ink text, dark buttons, footer, hero backdrop heaviest — 63 uses
accent brand highlight: logo dot, badges, hover states, newsletter band 36
cream raised surfaces, light buttons 23
clay image placeholders while photography is missing 14
rust the "AUTUMN 26" eyebrow chip 7
sand page background 4
parchment text on rust 4
dune the ticker strip under the hero 3
gold hover state on the hero's primary button 2

Two pairings carry most of the design: ink on sand for the page, and accent for anything interactive. Get those right and the rest follow.

Watch the contrast pairs — the storefront assumes:

  • cream and parchment read against ink
  • ink reads against sand, cream and clay
  • white reads against accent (badges and the newsletter band use white text)

An accent lighter than about #999 will fail that last one.


Font

"fontFamily": "Space Grotesk"

Any Google Fonts family name, letters, digits and spaces only. The storefront loads weights 400 and 700, so pick a family that has both. The name must match Google's exactly — DM Sans, not DM-Sans.


Style

Colour is not a design. Two themes with the same shapes and spacing read as the same theme repainted, which is why a theme also carries style.

Every field is optional; anything you leave out keeps the platform default, which is the original design.

Token Default What it changes
radiusPill 9999px buttons, chips, badges — the biggest single lever
radiusCard 2px image wells and tiles
radiusPanel 8px panels and authored blocks
imageAspect 4 / 5 product crop; drives the page's silhouette
controlHeight 44px buttons and inputs
typeScale 1 multiplier on every display heading
trackDisplay -0.02em tracking on large type
trackLabel 0.08em tracking on small uppercase labels
labelCase uppercase none to stop shouting
gutter / gutterWide 20px / 40px page margins
sectionY 64px rhythm between sections — airy against dense
shadowSm/Md/Lg soft none for a flat design
overlay rgba(0,0,0,.55) scrim over hero and tile imagery
fontBody system-ui body face (the display face is fontFamily)

Construction

Three of them are not values but a choice between layouts. Each names a component the storefront actually has, so the set is closed.

Token Options
headerLayout inline logo left, nav alongside
centered wordmark over its own nav row
heroLayout overlay copy printed over a darkened image
split copy beside the photograph
categoryShape square cards with the label inside
circle a scrolling row of circles

A worked example — premium beauty, structurally opposite to the default:

"style": {
  "radiusPill": "2px",
  "radiusCard": "0px",
  "imageAspect": "3 / 4",
  "controlHeight": "52px",
  "trackLabel": "0.24em",
  "sectionY": "112px",
  "shadowSm": "none", "shadowMd": "none", "shadowLg": "none",
  "headerLayout": "centered",
  "heroLayout": "split",
  "categoryShape": "circle"
}

Layout

The front page the design is built around, as a list of sections. Activating the theme applies it.

Every section, and what each one takes, is in Sections. That page matters more than this one if you are writing a theme for someone else: it says which sections read the shop's own products and categories, and which carry copy you have to supply.

Activating a theme overwrites the store's front page. That is deliberate — a theme you can switch to without the page changing is a recolour, not a theme — and the previous arrangement is kept so it can be put back.

A theme with no layout leaves the page alone.


Up to 12 items. Each needs a label (1–40 chars) and an href.

{ "label": "Sale", "href": "/new-arrivals", "highlight": true }

highlight renders that one link in the accent colour — the design uses it for "Sale".

href must be a relative path starting with /. Absolute URLs are rejected; a theme cannot point a shop's navigation at another site.

Real paths: /, /new-arrivals, /wishlist, /bag, /search, /discover, /account.


Hero

Every field is optional; anything omitted falls back to a default.

Field Limit Notes
eyebrow 80 small chip above the headline
title 160 \n becomes a line break
subtitle 400 hidden if empty
primaryLabel / primaryHref 40 / 200 main button
secondaryLabel / secondaryHref 40 / 200 empty label hides the button
url 1024 desktop banner, must be https://
mobileUrl 1024 portrait crop; falls back to url

Leave url and mobileUrl as null for a portable theme. A URL only resolves on the deployment it came from; elsewhere the banner shows its placeholder and the colours, font and copy still work.


Up to 6 columns, each with up to 12 links. Same href rule as the menu.

"footer": {
  "tagline": "Your style, your rules.",
  "columns": [
    { "title": "SHOP", "links": [ { "label": "New Arrivals", "href": "/new-arrivals" } ] }
  ],
  "legal": "© 2026 My Store",
  "social": "Instagram · TikTok"
}

The storefront lays the columns out automatically, so 2 columns and 6 both look deliberate.


Two ways to build one

Fork an existing theme — the quickest route. In the admin, open Themes, press + NEW THEME (it starts as a copy of the live design), change what you want with the preview beside you, save, then EXPORT.

Write the JSON directly — export any theme as a starting point, edit it, and import it back. Import creates it inactive, so you can check it in the gallery before making it live.


Before you ship a theme

  • Import it into an empty store — one with no products and no uploaded images. That is how a stranger will first see it, and it catches themes that only look right against your own catalogue.
  • Check the header, hero, product row, newsletter band and footer. The gallery card preview shows all of them at once.
  • Check both surfaces: the storefront is a single responsive app, and the mobile layout uses the same tokens differently.
  • Set hero.url to null unless you are shipping the theme inside the same deployment as the image.
  • Give it a distinctive name. On import, a clash is suffixed (Midnight (imported)), never overwritten — but a clear name saves the recipient renaming it.

Limits at a glance

Field Rule
format exactly my-store.theme
name 1–80 characters
fontFamily 1–80, letters/digits/spaces, a real Google Fonts family
colors.* all 9 required, valid hex
style.* optional; values are length-capped and may not contain ;{}:
style.headerLayout inline or centered
style.heroLayout overlay or split
style.categoryShape square or circle
layout ≤ 40 sections; each type must be a known section
menu ≤ 12 items; label 1–40; href relative, starts with /
footer.columns ≤ 6; title 1–40
footer.columns[].links ≤ 12; label 1–40; href relative
hero.url, hero.mobileUrl https:// or null
Building a theme — Docs