Apply

Theme layer

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.

Quick decision rules

  1. Is this changing presentation only?

    YesTheme

    NoNext question

  2. Is this changing structure or behaviour?

    YesComponent

    NoNext question

  3. 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.

What belongs in the theme layer

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.

Tokens via theme imports

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.

CSS code example
@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);

CSS code example
/* 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.

CSS code example
/* 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);
}

Brand-specific partials (when tokens are not enough)

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.

CSS code example
/* theme/partials/product-card--acme.css — rare, brand-specific presentation */
@layer theme {
    .product-card {
        border-width: var(--border-m);
        box-shadow: var(--shadow-brand);
    }
}

CSS code example
/* 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.

Avoid maintenance traps

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.

Before, css code example
/* 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.

Before, css code example
/* Avoid: one-off colours on selectors */
.button {
    background: purple;
}

.checkout-button {
    background: #7a32ff;
}

.hero-button {
    background: rgb(118 45 255);
}

  • Override tokens on :root in theme imports, not per-component custom properties in theme.
  • Do not duplicate presentation components already get from var(--…).
  • Do not use theme as a hiding place for hacks or layout fixes.
  • Keep brand-specific component partials rare, small, and imported from the brand theme file.
  • If the change is temporary, it belongs in hacks, not theme.

Next useful pages