Designing a token-driven theming engine

Engineering · Published Jun 26, 2026 · 9 min read.

Engineering

Eighteen months ago our front-end carried fourteen hand-maintained colour stylesheets — one per theme, plus a fork for dark mode. Every brand tweak meant a fourteen-file pull request. Today a single CSS variable swap retheme the entire product. This is how we got there.

The problem with literal colours

The original system hard-coded hex values directly in components. A button knew it was #3B82F6. When design shipped a new accent, we hunted those literals across the codebase. Dark mode doubled the surface area, and contrast bugs slipped through on every release.

The fix was a layer of role tokens: semantic names like --surface, --text-muted and --accent that point at raw stops. Components reference roles only — never stops — so swapping the underlying palette retheme everything at once.

Rule of thumb

If a component references a raw hex value, it can only ever look right in one theme. Role tokens are the contract that makes light, dark and twelve accents work for free.

Three layers, one direction

We settled on a strict one-way dependency: primitives feed roles, roles feed components. Nothing reaches back up the chain.

  • Primitives — the raw scale (--blue-500, --gray-100). Never referenced by components.
  • Roles — semantic aliases that resolve per theme (--surface, --accent).
  • Components — consume roles exclusively. One stylesheet, every theme.
tokens.css
/* role layer — resolves per theme */
:root {
  --surface: var(--gray-50);
  --text-strong: var(--gray-900);
  --accent: var(--brand-500);
}
[data-theme="dark"] {
  --surface: var(--gray-900);
  --text-strong: var(--gray-50);
}

What we measured afterward

The migration paid for itself within a quarter. A new accent now ships in minutes, dark mode is guaranteed-correct by construction, and our contrast regressions dropped to zero because the role layer enforces accessible pairings centrally.

"The best theming system is the one your team forgets exists — it just works in every mode, every time."

If you are still maintaining per-theme stylesheets, the cost is compounding quietly. A role layer is a weekend of disciplined renaming followed by years of dividends.

DO

Devon Okafor

Staff Engineer

Devon leads the design-systems guild and has spent the last decade making front-ends boringly reliable. Writes about CSS architecture, performance and the unglamorous work that keeps products fast.

Comments

responses

You