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 */ }
}
  • status gates everything: only a live store serves catalog and checkout. A non-live store renders an “unavailable” state, not an error.
  • theme carries 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 properties

You 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:

  1. Palette — the core color roles (clinical, midnight, oceanic, warm-sand, luxe-mono, vivid, forest). The engine derives the rest.
  2. Type — a font pairing (grotesk, editorial, brutalist, humanist).
  3. 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), and footer.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.