The four things a theme declares
export const layali: ThemeDefinition = {
tokens: {light: LAYALI_TOKENS, dark: LAYALI_TOKENS_DEEP},
slots: {
header: LayaliHeader,
footer: LayaliFooter,
homeDefault: LayaliHome,
plpLayout: LayaliPlp,
pdpLayout: LayaliPdp,
productCard: LayaliProductCard,
// announcementBar and productGrid are omitted on purpose —
// they fall through to the default design.
},
pageVariants: {
cart: 'compact',
checkout: 'focused',
account: 'pills',
orderView: 'receipt',
},
defaultSettings: {
headerStyle: 'overlay',
heroOverlay: 'medium',
footerVariant: 'full',
cardAspect: 'portrait',
showSaleBadges: true,
},
};- tokens — a CSS-variable delta, light and dark. Not a full stylesheet: only what differs from the default.
- slots — a sparse map of slot name to a server component. Header, footer, home page, listing and product arrangement, the product card.
- blocks — a sparse map of page-builder section type to a component, so the merchant’s own sections render in the theme’s idiom.
- pageVariants — named looks for the shared cart, checkout, account and order pages.
A theme lives in two halves. The catalogue half carries the id, the bilingual name and description, whether it is premium, the preview image and the settings schema; it is dependency-free so both the server and the merchant dashboard can import it. The rendering half carries the tokens and components. The storefront degrades safely: an id it does not recognise renders as the default rather than failing the page.
Adding one
Register it in the catalogue
Add the definition with its id, bilingual names, premium flag and settings fields.
Create the theme module
A directory exporting a ThemeDefinition, registered in the storefront’s theme registry.
Add a preview image
One SVG under the public themes directory, named for the id.
Mirror the defaults
Every settings field needs a default in the theme’s defaultSettings, or the merchant sees an empty control.
Translate
Any new storefront string goes into both message catalogues. Build the Arabic rendering first.
The rules that fail silently
Every slot is a server component
One deployment serves every store, so the theme is a runtime value and nothing can be tree-shaken per store. Server components ship no code to the browser, so N themes cost N × 0 client bytes. A slot that becomes a client component puts every theme’s markup into every shopper’s bundle. Interactivity comes from the existing theme-neutral client leaves; a theme that genuinely needs bespoke client code loads it dynamically.
The theme id threads through caches, not the component
Components are not serializable cache arguments, so a cached scope never receives a slot component. It receives the theme id as a string and looks the component up inside the scope. The id then participates in the cache key, which is what makes switching a theme produce a clean miss instead of a half-repainted store.
Never read request data in a slot
Reading headers, cookies or search params inside a slot opts the whole store layout out of its prerendered shell, for every shopper. Anything a slot needs arrives as a prop — the locale included, for exactly this reason.
Tokens are a delta, and the merchant wins
The cascade runs from the base stylesheet, through the theme’s tokens, through the merchant’s theme settings, to their brand colour. A theme sets the character of a store — density, warmth, corner radius — and the merchant’s choices sit on top. A store whose owner picked a large corner radius keeps it on a theme that ships square corners.
Switching a theme never rewrites the home page
A theme changes how sections render, not which sections exist. It may recommend a preset, but applying it is a separate, explicitly confirmed, destructive action — because that is the thing merchants are actually afraid of.
Arabic first, and logical properties only
The default language is Arabic. Build and check the RTL rendering before the LTR one, and use logical properties throughout — start and end, never left and right. Every user-facing string exists in both catalogues.
Settings, and the two kinds of them
Theme settings are validated server-side against the theme’s declared fields at write time, for every theme present in the blob rather than only the active one — a merchant keeps configuration for themes they are not currently using. The storefront then parses defensively anyway: a hand-edited blob must never break rendering.
{
"v": 1,
"store": { "productsPerPage": 12, "gridColsDesktop": 4 },
"themes": { "layali": { "headerStyle": "overlay", "footerVariant": "compact" } }
}| Theme settings | Store settings | |
|---|---|---|
| Describe | The look the merchant picked | Their catalogue and shoppers |
| Declared in | Each theme’s fields | A shared list |
| Survive a theme switch | No — kept per theme, restored on switching back | Yes |
| Examples | Header and footer layout, density, badges | Products per page, grid columns |