Theming
A store’s look is driven by its manifest — brand, theme tokens, catalog metadata, and section layout — which the storefront fetches when it resolves the store. You theme by editing the manifest (in your merchant console) and, if you own the frontend, by styling against the tokens it exposes.
The manifest
When the store resolves (by tenant or host), it receives a manifest roughly shaped like:
{
"store": { "id": "…", "slug": "…", "status": "live" },
"brand": { "name": "…", "logo": "…" },
"domains": { "primary": "…", "aliases": ["…"] },
"theme": { /* color / type / layout tokens */ },
"catalog": { "storeKey": "…" },
"comms": { /* consent + compliance settings */ }
}statusgates everything: only alivestore serves catalog and checkout. A non-livestore renders an “unavailable” state, not an error.themecarries the brand’s colors, type, and layout tokens the starter maps onto CSS custom properties.comms/ compliance drives age gates, cookie consent, and the RUO disclaimers where applicable.
Styling against tokens
The starter exposes the theme as CSS custom properties (e.g.
--color-primary, --color-ink, --font-display). Style components against
those variables rather than hard-coded values, so a manifest theme change
reflows the whole store:
.button { background: var(--color-primary); }
.heading { font-family: var(--font-display, var(--font-heading)); }This is also how you keep a custom frontend on-brand without duplicating the palette — read the tokens, don’t re-declare them.
Sections
The manifest describes the page as a list of sections (hero, product grid, editorial blocks, etc.). The starter renders a section registry; add or reorder sections in the manifest and the store re-lays-out without a code change.
If you own the frontend, the same manifest is part of the public contract:
store.site.get() (GET /v1/tenants/{tenant}/site) returns the live store’s
active manifest — the authored section list, nav, and theme tokens — so a
custom frontend can render what the merchant authored instead of hard-coding
its own layout:
const { manifest, version } = await store.site.get();
const sections = manifest.template?.sections ?? []; // [{ type, props }, …] in render order
const theme = manifest.theme; // token tree -> CSS custom propertiesYou can still ignore it and lay out your own pages — the catalog API is the same either way — but nav and theme then silently diverge from the merchant console. An unknown tenant or a non-live store is a real 404, never an empty manifest.
Imagery
Product and brand images resolve through the asset host
(NEXT_PUBLIC_ASSET_BASE_URL, or the platform default). Image references in the
catalog are opaque asset refs, not raw URLs — resolve them through the asset
component/base so variants (thumb/card/hero) and the CDN are handled for you.
Compliance surfaces
For RUO / research-use stores, the manifest’s compliance settings drive:
- Age gate — checkbox or interstitial, server-enforced at checkout when on.
- Cookie consent — gates non-essential tracking (e.g. post-order marketing beacons) per the visitor’s region.
- Disclaimers — the “For Research Use Only” copy in the footer / checkout.
Don’t hand-roll these — they’re driven by the manifest so they stay consistent and auditable across stores.
Theme presets & the matrix
You don’t have to hand-author a token tree per store. lib/theme-presets.ts
composes a store’s look from three independent axes:
- Palette — the core color roles (
clinical,midnight,oceanic,warm-sand,luxe-mono,vivid,forest). The engine derives the rest. - Type — a font pairing (
grotesk,editorial,brutalist,humanist). - Structure — architectural variants read from the token tree:
nav.style,hero.style(set it to off to drop the hero — the hero is a toggle), andfooter.style(minimal/columns/centered).
import { composeThemeTokens, themeMatrix, FEATURED_THEMES } from "@/lib/theme-presets";
// One theme for a store's manifest:
const tokens = composeThemeTokens({ palette: "midnight", type: "grotesk", structure: "space-hero" });
// Every combination (7 × 4 × 4 = 112+ distinct, ready-to-ship looks):
const all = themeMatrix(); // [{ id, choice, tokens }, …]Because the axes are independent, a handful of presets already yields hundreds
of distinct storefronts with zero bespoke CSS. Pin a matrix id per store, or
start from a FEATURED_THEMES entry and override any individual token.