Design Tokens and Theming at Scale
TL;DR
Design tokens are named design decisions stored as data — color.brand.primary = #2563eb, space.4 = 16px — the single source of truth shared by design and every platform. They’re tiered: primitive (raw values) → semantic/alias (color.text.danger) → component (button.bg). The modern pipeline is Figma → tokens (JSON, increasingly the W3C DTCG format) → Style Dictionary → CSS custom properties / iOS / Android outputs. At runtime, CSS variables make theming (dark mode, multi-tenant) a matter of swapping values, not rebuilding.
Interview Q&A
Q: What are design tokens and why use them?
A: Platform-agnostic, named values for the atomic design decisions (color, spacing, typography, radius, shadow, motion). Instead of hard-coding #2563eb in 50 places, you reference color.brand.primary. Benefits: one source of truth, consistent design, themeable, and design↔code stay in sync (designers change the token, code picks it up). They scale a design system across products and platforms.
Q: What are the token tiers?
A:
| Tier | Example | Purpose |
|---|---|---|
| Primitive / global | blue.500 = #2563eb, space.4 = 16px |
the raw palette/scale; no meaning |
| Semantic / alias | color.text.danger → red.600 |
intent; what it’s for |
| Component | button.primary.bg → color.action |
per-component, optional |
Components reference semantic tokens, semantic reference primitives. That indirection is what lets you re-theme: dark mode remaps semantic→primitive without touching components.
Q: How do CSS custom properties power theming?
A: Define tokens as CSS variables, theme by overriding them on a scope:
:root { --color-bg: #fff; --color-text: #111; }
[data-theme="dark"] { --color-bg: #0b0b0b; --color-text: #f5f5f5; }
.card { background: var(--color-bg); color: var(--color-text); }
Flip data-theme on <html> and the whole tree re-themes — no re-render, no prop drilling, cascades to portals. For OS sync, default from @media (prefers-color-scheme: dark) and let an explicit toggle override. See ../02_css/.
Q: What is Style Dictionary’s role?
A: It’s a build tool that transforms one token source (JSON/YAML) into many platform outputs: CSS variables, SCSS, JS/TS constants, iOS (Swift), Android (XML). You author tokens once; it generates per-platform artifacts with transforms (e.g., px→pt, hex→rgba). This is how a token change propagates everywhere from a single edit, and how design and native apps stay consistent.
Q: How do you do multi-tenant / white-label theming?
A: Keep components referencing semantic tokens; per tenant, provide a different set of token values (a theme bundle) injected as CSS variables at runtime (from tenant config) or built per tenant. Because components never hard-code values, a new tenant is “a new token file,” not a code change. Runtime injection (set CSS vars from an API response on load) supports many tenants from one build.
Q: Runtime vs build-time theming — trade-offs?
A:
- Build-time (compile a theme into CSS) — zero runtime cost, but a new theme needs a build/deploy; bad for many tenants.
- Runtime (CSS variables swapped live) — instant theme switching and unlimited tenants from one bundle; tiny runtime cost, and you must avoid FOUC by setting the theme before first paint (inline script in
<head>). CSS variables make runtime theming cheap, so it’s the common choice.
Gotchas / edge cases
- Skipping the semantic tier — components referencing primitives (
blue.500) directly can’t be re-themed; you lose the whole point. Always go through semantic tokens. - FOUC / theme flash — reading theme from
localStoragein React after hydration flashes the wrong theme; setdata-themevia a blocking inline script in<head>before paint (../19_rendering_modes/). - Tokens in JS vs CSS — JS token objects can’t be overridden by the cascade and bloat the bundle; prefer CSS variables for themeable values, JS tokens only where you must compute.
- Naming churn — renaming tokens breaks consumers; treat token names as an API (deprecate, don’t silently rename).
- Contrast across themes — a token palette that passes contrast in light mode may fail in dark mode; verify both (../16_accessibility/01_wcag_and_pour.md).
- DTCG format is still stabilizing — the W3C Design Tokens spec is in progress; tooling support varies.
What a senior is expected to say
- “Tokens are named design decisions in three tiers — primitive, semantic, component. Components reference semantic tokens so re-theming remaps values without touching components.”
- “CSS custom properties make theming a value swap on a scope — dark mode and multi-tenant for free, cascading to portals, no re-render.”
- “Style Dictionary turns one token source into CSS/JS/iOS/Android outputs; a token edit propagates everywhere.”
- “Runtime theming via CSS vars supports many tenants from one build — just guard against the theme-flash with a pre-paint script.”
Cross-references
- CSS custom properties / dark mode mechanics: ../02_css/
- MUI theming (a token system in practice): MaterialUI/advanced_theming.md
- Theme flash on SSR/hydration: ../19_rendering_modes/
- Contrast across themes: ../16_accessibility/01_wcag_and_pour.md
Further reading
- W3C Design Tokens Community Group format: https://tr.designtokens.org/format/
- Style Dictionary: https://styledictionary.com/
- MDN — Using CSS custom properties: https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties