Design tokens
Define the variables theme overrides so components stay stable.
Apply
Theme changes presentation, not structure. In most projects it is a thin layer on top of the work settings, base, layout, and components already did: import a theme/*.css file, override:root token values, and let existing selectors pick up the new brand without hunting through component files.
Is this changing presentation only?
YesTheme
NoNext question
Is this changing structure or behaviour?
YesComponent
NoNext question
Is this temporary or urgent?
YesHacks, not theme
No
If a visual change needs detective work to explain, the architecture is already drifting.
For base vs theme vs component ownership, seedecision trees. Theme should be predictable — presentation only, not structure, behaviour, or emergency fixes.
Theme is where brand skins land: token values that differ per brand or campaign, and occasionally a small presentation-only partial when tokens alone are not enough. Components should already consume shared variables from design tokens; theme retargets those variables, not individual component files.
Theme does not contain layout fixes, accessibility repairs, JavaScript state handling, or emergency overrides pretending not to be hacks. If a change needs new structure or behaviour, that belongs in layout or components — not a theme workaround.
Wire the theme layer in your root import map, then keep each brand in its own file. When the display font or accent colour changes, update the token on :root in that theme file. Because components already use var(--…), the new values take precedence without opening every partial.
@layer legacy, settings, base, utilities, layout, components, theme, hacks;
@import './settings/tokens.css' layer(settings);
@import './components/product-card.css' layer(components);
@import './theme/brand-a.css' layer(theme);/* theme/brand-a.css — override tokens, not components */
:root {
--ff-display: 'Acme Display', system-ui, sans-serif;
--c-accent: #b4002d;
--c-surface: #fffaf5;
--br-card: 0.5rem;
}Components stay in the components layer and reference tokens once. Switching brand means swapping (or adding) a theme import, not editing component CSS.
/* components/product-card.css — written once in the components layer */
.product-card {
font-family: var(--ff-display);
background: var(--c-surface);
border-radius: var(--br-card);
color: var(--c-text);
}Large multi-brand sites sometimes need presentation that tokens cannot express cleanly — a thicker border, a brand-only shadow, a campaign-specific radius on one pattern. That is still theme work, but isolate it: add another partial under theme/ and import it from the brand’s theme file. Do not scatter overrides across component files or re-declare properties on every selector when a token change would do.
/* theme/partials/product-card--acme.css — rare, brand-specific presentation */
@layer theme {
.product-card {
border-width: var(--border-m);
box-shadow: var(--shadow-brand);
}
}/* theme/brand-a.css */
:root {
--c-accent: #b4002d;
--shadow-brand: 0 4px 24px rgb(180 0 45 / 0.15);
}
@import './partials/product-card--acme.css';Keep these partials small and rare. If the brand needs different layout or behaviour, the change belongs in layout or components for that brand — not a growing theme file that rewrites architecture.
Setting token values on components from the theme layer — custom properties on .button, re-applyingbackground on .product-card because the token names are already wired — makes brand changes hard to find, easy to miss, and painful to review. Prefer:root in the brand theme file; let components keep doing their job.
/* Avoid: theme re-targeting component-level custom properties */
@layer theme {
.button {
--button-background: var(--c-accent);
}
.product-card {
background: var(--c-surface);
border-radius: var(--br-card);
}
}Random one-off colours on selectors are the same problem without the indirection: branding becomes guesswork instead of a single import.
/* Avoid: one-off colours on selectors */
.button {
background: purple;
}
.checkout-button {
background: #7a32ff;
}
.hero-button {
background: rgb(118 45 255);
}:root in theme imports, not per-component custom properties in theme.var(--…).