Design Tokens: One Change, Every Platform
You open a pull request that changes the primary brand color. On web, the new color looks correct. On iOS, it’s two shades darker because someone hardcoded the old hex value in three SwiftUI views. On Android, it’s the original color entirely because the XML resource file was never connected to any shared source. The design team’s Figma file shows a fourth shade that matches none of the three implementations. Four platforms. Four different blues. Zero coordination.
Four platforms, four workflows, nobody noticed until a quarterly review. Multiply by every color, spacing value, and font size. That’s why consistency breaks at scale.
- Design tokens are the single source of truth for visual properties. Color, spacing, typography, elevation. Defined once, generated for every platform.
- Three taxonomy levels: global tokens (raw values), alias tokens (semantic meaning like
color-action-primary), component tokens (scoped to specific widgets). - Token pipelines generate platform outputs automatically. CSS custom properties, iOS Swift enums, Android XML resources, Figma variables. All from the same JSON source.
- Dark mode is the first real test of token architecture. If switching themes requires touching component code, tokens aren’t properly layered.
- FOUT (Flash of Unstyled Tokens) happens when token CSS loads after component CSS. Inline critical token values or ensure they load first.
The W3C Design Tokens Community Group is standardizing the interchange format.
Token Taxonomy: Global, Alias, Component
- Design team agrees on a spacing scale (e.g., 4px base unit)
- Color palette finalized with light and dark variants for every semantic role
- Typography scale defined with at most 6-8 size steps
- At least one consuming platform (web, iOS, Android) ready to integrate token output
- Git repository established as the canonical source for token definitions
A flat list of 800 names is a constants file, not a system. Three layers hold up in production.
{
"global": {
"blue-500": { "value": "#1A2980" },
"space-4": { "value": "16px" },
"font-size-3": { "value": "1rem" }
},
"alias": {
"color-interactive": { "value": "{blue-500}" },
"color-surface": { "value": "{white}", "darkValue": "{gray-900}" },
"spacing-component-gap": { "value": "{space-4}" }
},
"component": {
"button-primary-bg": { "value": "{color-interactive}" },
"card-padding": { "value": "{spacing-component-gap}" }
}
}
Global tokens: raw values (blue-500: #1A2980). Never reference directly in components. Alias tokens: semantic meaning (color-interactive → blue-500). Theme switching lives here. Component tokens: bind decisions to specific widgets (button-primary-bg → color-interactive).
Skip alias tokens and reference globals directly? You’ll rewrite every component when dark mode arrives. Weeks of refactoring that a single abstraction layer would have prevented.
Multi-Platform Token Distribution
| Platform | Output Format | Generated From | Example |
|---|---|---|---|
| Web | CSS custom properties | tokens.json → variables.css | --color-interactive: #1A2980 |
| iOS | Swift constants | tokens.json → Colors.swift | static let interactive = UIColor(hex: "#1A2980") |
| Android | XML resources | tokens.json → colors.xml | <color name="interactive">#1A2980</color> |
| React Native | JS style objects | tokens.json → tokens.ts | interactive: '#1A2980' |
| Figma | Plugin sync | tokens.json → Figma variables | Bidirectional with Tokens Studio |
Style Dictionary is the standard tool for this transformation. Define tokens once. Run a build. Get CSS custom properties, iOS Swift constants, Android XML values, and React Native style objects from the same source file.
In production, plan for 20-30 custom transforms. iOS needs CGFloat, Android needs dp, CSS needs rem for fonts but px for borders. That’s where the real engineering hours go.
Token Studio bridges Figma to Git. Designers change tokens in Figma, the plugin opens a PR. CI runs Style Dictionary. No more “I updated colors in Figma, can someone update the code?”
Platform packages are versioned and published to internal registries. Consuming apps pin and upgrade explicitly.
Theme Switching Architecture
background: white hardcoded in 200 components needs 200 changes for dark mode. background: var(--color-surface) needs one. A data-theme attribute on the root switches values.
Check localStorage first, fall back to prefers-color-scheme, store the user’s toggle. Fix the flash of wrong theme (FOWT) with a blocking inline <script> in <head> that sets data-theme before CSS renders.
The Dark Mode Tax
Shadows disappear on dark backgrounds. You need elevated surface tokens (surface-elevated-1, -2, -3). Images need treatment. Screenshots on light UI look jarring on dark. Contrast ratios shift. Text passing AA on white often fails on dark gray. Every token pair needs verification per theme.
Total effort routinely exceeds the initial estimate by a wide margin. The token architecture contains it to the token layer instead of spreading across every component. Effective design system architecture treats dark mode as day-one infrastructure.
Dark mode token mapping checklist
- Surface hierarchy: Define
surface-1,surface-2,surface-3for light and dark. In dark mode, elevated surfaces are lighter, not darker. Shadows lose their visual effect on dark backgrounds, so surface elevation replaces shadow depth. - Text contrast: Every text-on-surface combination needs WCAG AA verification per theme. Pure white (
#FFFFFF) text on dark backgrounds causes eye strain. Use#E0E0E0or lighter grays. - Image treatment: Add a subtle overlay or reduced brightness to screenshots and illustrations that assume a light background. SVG icons should reference token colors, not hardcoded values.
- Accent colors: Saturated colors that pass contrast on white often fail on dark gray. Maintain a parallel set of accent tokens with adjusted lightness values per theme.
- Borders and dividers:
1px solid #E0E0E0vanishes on a dark surface. Divider tokens need separate light and dark mappings.
Token Versioning and Breaking Changes
Tokens are an API. Rename color-primary to color-brand-primary and every consumer breaks. Semantic versioning: new tokens are minor, value changes are patches, renames/deletions are major.
CI should diff against the last published version and classify each change. Block the merge until the version bump matches the severity. Without this, a designer renames in Figma, sync pushes to Git, and three apps break.
CI Pipelines for Token Validation
Three CI stages: schema validation (type, value, description required), contrast verification (every foreground-background pair checked per theme against WCAG AA), and visual regression (Chromatic/Percy on Storybook). 45-90 seconds of build time versus hours of production debugging. CI/CD pipelines make this trivial.
The Handoff Gap
Token Studio makes Figma and Git share the same source. Designer changes a token, plugin opens a PR, CI validates, engineer merges. The “Figma vs. code is the source of truth” debate dies. The token JSON file is the source. Neither side can drift.
Chromatic’s visual diff catches changes that are numerically correct but look wrong in context. Building web applications at scale requires this design-to-code automation.
When Tokens Become Tech Debt
| When tokens add value | When tokens add overhead |
|---|---|
| Multiple platforms consuming the same visual language | Single-platform product with no mobile roadmap |
| More than 2 teams building UI against the same design system | Solo developer or small team with direct Figma-to-code workflow |
| Dark mode, theming, or multi-brand requirements exist or are planned | Single theme with no foreseeable need for variation |
| Design and engineering teams are separate and need a shared contract | Designer-developer working as a pair on every component |
Three rot signals to watch for:
Token explosion past 2,000 where developers create tokens instead of discovering existing ones. Fix with enforced naming conventions and a searchable token catalog. Orphan tokens referenced by zero apps. Run a quarterly audit against actual usage and deprecate aggressively. Alias bypass where components reference globals directly (--blue-500 instead of --color-interactive). Lint rules catch this automatically.
Don’t: Reference global tokens directly in component CSS: background: var(--blue-500). When you need dark mode or a second brand, every component needs rewriting.
Do: Reference alias tokens: background: var(--color-interactive). Theme switching changes the alias mapping. Components never know the difference.
What the Industry Gets Wrong About Design Tokens
“CSS variables are design tokens.” CSS variables are one output format. Tokens are the source definition plus the pipeline that generates outputs for every platform. A team maintaining CSS variables manually in a stylesheet has CSS variables, not design tokens.
“Dark mode is just inverting colors.” Dark mode requires a separate set of alias token mappings that account for contrast, perceived brightness, and elevation hierarchy. A simple inversion produces text that’s too bright, surfaces that lack depth, and contrast ratios that fail WCAG. Proper dark mode is a full design exercise expressed through the token layer.
The design systems engineering guide covers component architecture on top of the token layer. Treat tokens like any other shared dependency: versioned, tested, distributed through packages, monitored for drift. Otherwise: four platforms, four different blues, zero coordination.