Design Token System in Practice: 5 Patterns for Scalable Theme & Multi-Brand Architecture
Design Tokens: The Atomic Infrastructure of Design Systems
The biggest pain point in design systems isn't lacking components—it's theme switching via overrides, brand extensions via duplication, and dark mode via patches. Design Tokens abstract design decisions like colors, spacing, and typography into platform-agnostic key-value pairs, enabling "define once, consume everywhere." In 2026, the W3C Design Token specification has reached Candidate Recommendation, Style Dictionary 4.0+ supports multi-platform output, and Design Tokens have become the standard infrastructure for design systems.
This article walks through 5 core patterns, covering the full pipeline from Token layering → theme switching → multi-brand → build pipeline → runtime dynamics.
Core Concepts
| Concept | Description |
|---|---|
| Design Token | Atomic representation of a design decision as a key-value pair |
| Global Token | Raw global values, e.g., blue-500: #3b82f6 |
| Alias Token | Semantic alias, e.g., primary: {blue-500} |
| Component Token | Component-level token, e.g., button-primary-bg: {primary} |
| Theme | A collection of token values, e.g., light/dark theme |
| Multi-Brand | Same design system supporting multiple brand token sets |
| Style Dictionary | Token compilation tool with multi-platform output |
| W3C DTCG | Design Token Community Group specification |
Problem Analysis: 5 Challenges in Design Token Systems
- Token layering chaos: Unclear boundaries between global/semantic/component layers, overly long reference chains
- Theme switching performance: Runtime theme switching causes repaint flicker
- Multi-brand extension: Large token differences between brands, complex inheritance
- Build output: Varying output formats across platforms (CSS/JS/Swift/Kotlin)
- Design collaboration: Designers use Figma, developers use code—token sync is difficult
Step-by-Step: 5 Design Token Patterns
Pattern 1: Three-Layer Token Architecture
// tokens/global/color.json
{
"color": {
"blue": {
"100": { "value": "#dbeafe", "type": "color" },
"500": { "value": "#3b82f6", "type": "color" },
"900": { "value": "#1e3a8a", "type": "color" }
},
"gray": {
"50": { "value": "#f9fafb", "type": "color" },
"900": { "value": "#111827", "type": "color" }
},
"red": {
"500": { "value": "#ef4444", "type": "color" }
},
"green": {
"500": { "value": "#22c55e", "type": "color" }
}
}
}
// tokens/semantic/color.json
{
"color": {
"primary": { "value": "{color.blue.500}", "type": "color" },
"primary-hover": { "value": "{color.blue.600}", "type": "color" },
"background": { "value": "{color.gray.50}", "type": "color" },
"surface": { "value": "#ffffff", "type": "color" },
"text": { "value": "{color.gray.900}", "type": "color" },
"text-secondary": { "value": "{color.gray.500}", "type": "color" },
"error": { "value": "{color.red.500}", "type": "color" },
"success": { "value": "{color.green.500}", "type": "color" },
"border": { "value": "{color.gray.200}", "type": "color" }
}
}
// tokens/component/button.json
{
"button": {
"primary": {
"bg": { "value": "{color.primary}", "type": "color" },
"bg-hover": { "value": "{color.primary-hover}", "type": "color" },
"text": { "value": "#ffffff", "type": "color" },
"border-radius": { "value": "{radius.md}", "type": "borderRadius" },
"padding-x": { "value": "{spacing.4}", "type": "spacing" },
"padding-y": { "value": "{spacing.2}", "type": "spacing" }
},
"secondary": {
"bg": { "value": "{color.surface}", "type": "color" },
"text": { "value": "{color.primary}", "type": "color" },
"border": { "value": "{color.border}", "type": "color" }
}
}
}
Pattern 2: Theme Switching (Light/Dark)
// tokens/semantic/color.dark.json
{
"color": {
"primary": { "value": "{color.blue.400}", "type": "color" },
"background": { "value": "{color.gray.900}", "type": "color" },
"surface": { "value": "#1f2937", "type": "color" },
"text": { "value": "{color.gray.50}", "type": "color" },
"text-secondary": { "value": "{color.gray.400}", "type": "color" },
"border": { "value": "{color.gray.700}", "type": "color" }
}
}
/* Build output: tokens.css */
:root {
--color-primary: #3b82f6;
--color-background: #f9fafb;
--color-surface: #ffffff;
--color-text: #111827;
--color-border: #e5e7eb;
--button-primary-bg: var(--color-primary);
--button-primary-text: #ffffff;
}
[data-theme="dark"] {
--color-primary: #60a5fa;
--color-background: #111827;
--color-surface: #1f2937;
--color-text: #f9fafb;
--color-border: #374151;
--button-primary-bg: var(--color-primary);
}
// Theme switcher
type Theme = 'light' | 'dark' | 'system';
class ThemeManager {
private mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
private listeners: Set<(theme: Theme) => void> = new Set();
init() {
const saved = localStorage.getItem('theme') as Theme | null;
if (saved && saved !== 'system') {
this.apply(saved);
} else {
this.apply(this.mediaQuery.matches ? 'dark' : 'light');
}
this.mediaQuery.addEventListener('change', (e) => {
if (this.getStored() === 'system') {
this.apply(e.matches ? 'dark' : 'light');
}
});
}
setTheme(theme: Theme) {
localStorage.setItem('theme', theme);
if (theme === 'system') {
this.apply(this.mediaQuery.matches ? 'dark' : 'light');
} else {
this.apply(theme);
}
this.listeners.forEach((fn) => fn(theme));
}
private apply(mode: 'light' | 'dark') {
document.documentElement.setAttribute('data-theme', mode);
}
private getStored(): Theme {
return (localStorage.getItem('theme') as Theme) || 'system';
}
onChange(fn: (theme: Theme) => void) {
this.listeners.add(fn);
return () => this.listeners.delete(fn);
}
}
Pattern 3: Multi-Brand Token Inheritance
// brands/brand-a/tokens.json
{
"color": {
"primary": { "value": "#3b82f6", "type": "color", "$extensions": { "brand": "A" } },
"primary-hover": { "value": "#2563eb", "type": "color" }
},
"typography": {
"font-family": { "value": "Inter, sans-serif", "type": "fontFamily" },
"heading-weight": { "value": "700", "type": "fontWeight" }
},
"radius": {
"base": { "value": "8px", "type": "borderRadius" }
}
}
// brands/brand-b/tokens.json
{
"color": {
"primary": { "value": "#8b5cf6", "type": "color", "$extensions": { "brand": "B" } },
"primary-hover": { "value": "#7c3aed", "type": "color" }
},
"typography": {
"font-family": { "value": "Poppins, sans-serif", "type": "fontFamily" },
"heading-weight": { "value": "600", "type": "fontWeight" }
},
"radius": {
"base": { "value": "4px", "type": "borderRadius" }
}
}
// Brand switcher
type Brand = 'brand-a' | 'brand-b';
async function switchBrand(brand: Brand) {
const brandCSS = await fetch(`/tokens/${brand}.css`).then((r) => r.text());
let style = document.getElementById('brand-tokens') as HTMLStyleElement;
if (!style) {
style = document.createElement('style');
style.id = 'brand-tokens';
document.head.appendChild(style);
}
style.textContent = brandCSS;
document.documentElement.setAttribute('data-brand', brand);
localStorage.setItem('brand', brand);
}
Pattern 4: Style Dictionary Build Pipeline
// style-dictionary.config.mjs
import StyleDictionary from 'style-dictionary';
StyleDictionary.registerTransform({
name: 'size/pxToRem',
type: 'value',
matcher: (token) => token.type === 'spacing' || token.type === 'fontSize',
transformer: (token) => {
const base = 16;
const val = parseFloat(token.value);
return `${val / base}rem`;
},
});
StyleDictionary.registerTransformGroup({
name: 'custom/css',
transforms: [
'attribute/cti',
'name/kebab',
'time/seconds',
'size/pxToRem',
'color/css',
],
});
const config = {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'custom/css',
buildPath: 'dist/css/',
files: [
{
destination: 'tokens.css',
format: 'css/variables',
options: { outputReferences: true },
},
],
},
js: {
transformGroup: 'js',
buildPath: 'dist/js/',
files: [
{
destination: 'tokens.js',
format: 'javascript/es6',
},
{
destination: 'tokens.d.ts',
format: 'typescript/es6-declarations',
},
],
},
json: {
transformGroup: 'json',
buildPath: 'dist/json/',
files: [
{
destination: 'tokens.json',
format: 'json',
},
],
},
ios: {
transformGroup: 'ios-swift-separate',
buildPath: 'dist/ios/',
files: [
{
destination: 'DesignTokens.swift',
format: 'ios-swift/class.swift',
className: 'DesignTokens',
},
],
},
android: {
transformGroup: 'android',
buildPath: 'dist/android/',
files: [
{
destination: 'design_tokens.xml',
format: 'android/resources',
},
],
},
},
};
export default config;
# Build all platforms
npx style-dictionary build
# Watch token changes
npx style-dictionary build --watch
Pattern 5: Runtime Dynamic Tokens
// runtime-token-engine.ts
interface TokenValue {
value: string;
type: string;
description?: string;
}
class RuntimeTokenEngine {
private tokens: Map<string, TokenValue> = new Map();
private subscribers: Map<string, Set<(value: string) => void>> = new Map();
loadFromCSS() {
const styles = getComputedStyle(document.documentElement);
const allVars = Array.from(document.styleSheets)
.flatMap((sheet) => {
try {
return Array.from(sheet.cssRules);
} catch {
return [];
}
})
.filter((rule) => rule instanceof CSSStyleRule)
.flatMap((rule: CSSStyleRule) => {
const props: string[] = [];
for (let i = 0; i < rule.style.length; i++) {
const prop = rule.style[i];
if (prop.startsWith('--')) {
props.push(prop);
}
}
return props;
});
const uniqueVars = [...new Set(allVars)];
uniqueVars.forEach((v) => {
const val = styles.getPropertyValue(v).trim();
if (val) {
this.tokens.set(v, { value: val, type: 'unknown' });
}
});
}
setToken(name: string, value: string) {
const varName = name.startsWith('--') ? name : `--${name}`;
document.documentElement.style.setProperty(varName, value);
this.tokens.set(varName, { value, type: 'unknown' });
this.notify(varName, value);
}
getToken(name: string): string | undefined {
const varName = name.startsWith('--') ? name : `--${name}`;
return getComputedStyle(document.documentElement)
.getPropertyValue(varName)
.trim();
}
subscribe(name: string, callback: (value: string) => void) {
const varName = name.startsWith('--') ? name : `--${name}`;
if (!this.subscribers.has(varName)) {
this.subscribers.set(varName, new Set());
}
this.subscribers.get(varName)!.add(callback);
return () => this.subscribers.get(varName)?.delete(callback);
}
private notify(name: string, value: string) {
this.subscribers.get(name)?.forEach((fn) => fn(value));
}
}
Pitfall Guide
Pitfall 1: Token Reference Chains Too Deep
// ❌ Wrong: 5-layer reference chain, hard to debug
{
"btn-bg": { "value": "{button.primary.bg}" },
"button.primary.bg": { "value": "{color.primary}" },
"color.primary": { "value": "{blue.500}" }
}
// ✅ Correct: Keep within 3 layers
{
"button-primary-bg": { "value": "{color.primary}" },
"color.primary": { "value": "#3b82f6" }
}
Pitfall 2: Hardcoded Color Values
/* ❌ Wrong: Hardcoded colors */
.card { background: #ffffff; border: 1px solid #e5e7eb; }
/* ✅ Correct: Use token variables */
.card { background: var(--color-surface); border: 1px solid var(--color-border); }
Pitfall 3: Theme Switch Flicker
<!-- ❌ Wrong: Theme set after JS loads, causing flicker -->
<!-- ✅ Correct: Inline script blocks rendering -->
<script>
(function() {
const theme = localStorage.getItem('theme');
if (theme === 'dark' || (!theme && matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
</script>
Pitfall 4: Component Token Overuse
// ❌ Wrong: Creating a token for every component property
{
"card-padding-top": { "value": "{spacing.4}" },
"card-padding-right": { "value": "{spacing.4}" },
"card-padding-bottom": { "value": "{spacing.4}" },
"card-padding-left": { "value": "{spacing.4}" }
}
// ✅ Correct: Reuse semantic tokens
{
"card-padding": { "value": "{spacing.4}" }
}
Pitfall 5: Ignoring Token Type Validation
// ❌ Wrong: No type validation, color token receives spacing value
// ✅ Correct: Validate at build time
StyleDictionary.registerFilter({
name: 'isValidColor',
matcher: (token) => {
if (token.type === 'color') {
return /^#([0-9a-f]{3}){1,2}$/i.test(token.value) ||
token.value.startsWith('{');
}
return true;
},
});
Error Troubleshooting
| # | Error Message | Cause | Solution |
|---|---|---|---|
| 1 | Reference not found: {color.xxx} |
Token references non-existent key | Check reference path spelling |
| 2 | Circular reference detected |
Token circular reference | Break the circular reference chain |
| 3 | Invalid token value |
Invalid token value format | Validate color/spacing format |
| 4 | CSS variable not applied |
Selector specificity overridden | Increase CSS variable selector specificity |
| 5 | Theme flash on load |
Theme init after render | Inline blocking script to set theme |
| 6 | Style Dictionary build failed |
Token file format error | Check JSON syntax and $extensions |
| 7 | Token not found in output |
Token not matched by source | Check source glob path |
| 8 | Platform output mismatch |
Inconsistent transform rules | Unify transformGroup config |
| 9 | Figma sync conflict |
Figma tokens out of sync with code | Use Token Studio for bidirectional sync |
| 10 | Runtime token override fails |
Runtime override not taking effect | Check CSS variable priority and scope |
Advanced Optimization
- Token Studio Integration: Figma plugin for bidirectional design-code token sync
- Token Versioning: Semantic versioning + automatic changelog generation
- A/B Test Tokens: Runtime dynamic token switching for visual A/B testing
- Token Lint: ESLint plugin to detect hardcoded design values in code
- Token Documentation Site: Auto-generated visual token documentation
Comparison
| Dimension | CSS Variables | Design Tokens | Tailwind Config | CSS-in-JS Theme |
|---|---|---|---|---|
| Platform Support | Web only | Web/iOS/Android | Web only | Web only |
| Type Safety | ❌ | ✅ | ✅ | ✅ |
| Theme Switching | ✅ | ✅ | ✅ | ✅ |
| Multi-Brand | Manual | Native support | Config switch | Config switch |
| Design Collaboration | ❌ | ✅ Figma sync | ❌ | ❌ |
| Build Output | CSS | CSS/JS/Swift/Kotlin | CSS/JS | JS |
Summary: Design Token systems are the key infrastructure for evolving design systems from "component libraries" to "design languages." The three-layer token architecture (global → semantic → component) combined with Style Dictionary multi-platform output establishes a single source of truth for design decisions. In 2026, the maturing W3C DTCG specification will drive token standardization, making multi-brand and runtime dynamic tokens standard features for enterprise design systems.
Recommended Online Tools
- JSON Formatter: /en/json/format
- Hash Calculator: /en/encode/hash
- cURL to Code: /en/dev/curl-to-code
Try these browser-local tools — no sign-up required →