Summary
Design tokens are named values — colors, spacing, radii — stored once and transformed to CSS custom properties, iOS Swift, Android XML, or any output via a build tool such as Style Dictionary. A three-tier model (primitive → semantic → component) separates raw values from intent, making light/dark and brand swaps a single file change rather than a search-and-replace.
Jump to the interview angleThree-tier token model
Tokens are organized in three layers. Primitive tokens are raw values: --color-blue-500: #3b82f6. They have no meaning beyond the value itself. Semantic tokens reference primitives and carry intent: --color-action-primary: var(--color-blue-500). Component tokens scope a semantic to one component: --button-bg: var(--color-action-primary). This indirection means swapping themes only touches the semantic layer — primitives and components stay unchanged.
CSS custom properties theme swap
Semantic tokens live on :root; the dark theme overrides them on [data-theme='dark']. Components reference only semantic tokens, so switching themes is one attribute change on <html>.
/* primitives — raw values, never used by components directly */
:root {
--color-gray-50: #f9fafb;
--color-gray-900: #111827;
--color-blue-500: #3b82f6;
}
/* semantic tokens — default (light) theme */
:root {
--color-surface: var(--color-gray-50);
--color-text-primary: var(--color-gray-900);
--color-action-primary: var(--color-blue-500);
}
/* dark theme override — only semantic layer changes */
[data-theme="dark"] {
--color-surface: var(--color-gray-900);
--color-text-primary: var(--color-gray-50);
--color-action-primary: var(--color-blue-500); /* same primitive, different context */
}
/* component — references semantic only */
.btn-primary {
background: var(--color-action-primary);
color: var(--color-surface);
}Toggle data-theme="dark" on <html> and every component updates instantly — zero JS rerenders, zero class churn.
Build-time vs runtime theming
| Approach | How it works | Perf cost | Flexibility | |
|---|---|---|---|---|
| Build-time (CSS files) | Style Dictionary emits one CSS file per theme at build | Zero runtime cost; ship only active theme | None | Theme switch requires page reload or separate stylesheet swap |
| Runtime (CSS custom props) | One stylesheet; JS sets `data-theme` attribute on `<html>` | Single CSS parse; no JS bundle cost per theme | Minimal | Instant switch; supports user preference + OS sync |
| Runtime (JS-in-CSS / CSS-in-JS) | Theme object injected into component styles at render | Re-runs style computation per render; ~20 ms overhead at scale | High | Fully dynamic; token values can be computed at runtime |
Flash of unstyled theme (FOUT)
When the active theme is set by JS after HTML parse, users see a flash of the default theme. Fix it by inlining a <script> that reads localStorage and sets data-theme before the first paint — or by defaulting to the OS preference with prefers-color-scheme in CSS and only overriding on explicit user choice.
Interview angle
Interviewers test whether you can explain why three tiers exist and how build-time vs runtime theming differ in cost and flexibility. Know Style Dictionary as the concrete tool.
Soundbite: "Semantic tokens decouple intent from value — change the theme file, not every component."
Key terms
- Design token
- A named design decision (color, spacing, radius) stored as a platform-agnostic value and transformed to target outputs.
- Primitive token
- A raw value with no semantic meaning, e.g. `--color-blue-500: #3b82f6`.
- Semantic token
- A token expressing intent by referencing a primitive, e.g. `--color-action-primary`.
- Style Dictionary
- Amazon's open-source build tool that transforms a token JSON source into CSS, Swift, Android XML, and other platform outputs.
- CSS custom property
- A variable declared with `--name: value` and read with `var(--name)`; reassignable at any scope for runtime theming.