Design Token Hierarchy
What Are Design Tokens, Really?
Think of design tokens like the periodic table for your UI. Hydrogen and oxygen are fundamental elements — you combine them to make water. Design tokens work the same way: they are the smallest, indivisible decisions in your design system. A color, a spacing value, a font size, a shadow, a border radius, a duration. Each one is a named, reusable value that every component in your system references instead of hardcoding.
Without tokens, you get this:
.button {
background-color: #6366f1;
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
}
.card {
border: 1px solid #e5e7eb;
padding: 16px;
border-radius: 6px; /* Same as button — coincidence or intentional? */
font-size: 14px; /* Same as button — should they always match? */
}
With tokens, every value has a name and a purpose:
:root {
--color-primary: #6366f1;
--color-border: #e5e7eb;
--space-2: 8px;
--space-4: 16px;
--radius-md: 6px;
--font-size-sm: 14px;
}
.button {
background-color: var(--color-primary);
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
font-size: var(--font-size-sm);
}
Now the relationship is explicit. When you change --radius-md, every component using it updates together. When your designer says "make the corners rounder," it is a single-line change instead of a search-and-replace across 200 files.
Design tokens are a contract between design and engineering. They say: "These are the exact values we agree on." Without tokens, design and code drift apart silently — the designer picks one blue, the engineer eyeballs a slightly different blue, and six months later you have 47 shades of blue in production. Tokens make drift impossible because both sides reference the same source of truth.
The Three-Tier Hierarchy
Here is the thing most people get wrong about tokens: they dump everything into a flat list. You end up with 400 CSS variables, no structure, and nobody can find anything. The solution is a three-tier hierarchy that separates raw values from intent from usage.
Tier 1: Global Tokens (The Palette)
Global tokens are raw values with no semantic meaning. They describe what the value is, not why you would use it.
{
"color": {
"blue": {
"50": { "value": "#eff6ff" },
"100": { "value": "#dbeafe" },
"500": { "value": "#3b82f6" },
"600": { "value": "#2563eb" },
"900": { "value": "#1e3a5a" }
},
"gray": {
"50": { "value": "#f9fafb" },
"200": { "value": "#e5e7eb" },
"700": { "value": "#374151" },
"900": { "value": "#111827" }
}
},
"space": {
"1": { "value": "4px" },
"2": { "value": "8px" },
"4": { "value": "16px" },
"8": { "value": "32px" }
},
"radius": {
"sm": { "value": "4px" },
"md": { "value": "6px" },
"lg": { "value": "8px" },
"full": { "value": "9999px" }
}
}
Notice: blue-500 tells you nothing about when to use it. That is intentional. Global tokens are a vocabulary, not a grammar.
Tier 2: Alias Tokens (Semantic Intent)
Alias tokens map meaning to global tokens. They answer "why would I use this value?"
{
"color": {
"primary": { "value": "{color.blue.600}" },
"primary-hover": { "value": "{color.blue.700}" },
"danger": { "value": "{color.red.600}" },
"bg-primary": { "value": "{color.white}" },
"bg-secondary": { "value": "{color.gray.50}" },
"text-primary": { "value": "{color.gray.900}" },
"text-secondary": { "value": "{color.gray.700}" },
"border": { "value": "{color.gray.200}" }
},
"space": {
"xs": { "value": "{space.1}" },
"sm": { "value": "{space.2}" },
"md": { "value": "{space.4}" },
"lg": { "value": "{space.8}" }
}
}
The {color.blue.600} syntax is a reference — the alias token points to the global token. Now color-primary means something. And when your brand color changes from blue to purple, you update one alias. Every component using color-primary follows.
Tier 3: Component Tokens (Scoped Overrides)
Component tokens are the most specific. They let individual components diverge from the alias without breaking the system.
{
"button": {
"bg": { "value": "{color.primary}" },
"bg-hover": { "value": "{color.primary-hover}" },
"text": { "value": "{color.white}" },
"radius": { "value": "{radius.md}" },
"padding-x": { "value": "{space.md}" },
"padding-y": { "value": "{space.sm}" }
},
"input": {
"border": { "value": "{color.border}" },
"border-focus": { "value": "{color.primary}" },
"radius": { "value": "{radius.md}" },
"bg": { "value": "{color.bg-primary}" }
}
}
Component tokens reference alias tokens, which reference global tokens. Three levels of indirection that buy you incredible flexibility.
Naming Conventions: Category-Type-Item-State
Good token names are self-documenting. Bad token names are a puzzle. The industry-standard convention is CTI (Category-Type-Item) with an optional State suffix.
category - type - item - state
color - bg - primary
color - text - primary
color - border - input - focus
space - gap - card
font - size - body
font - weight - heading
shadow - box - card - hover
radius - border - button
motion - duration - fast
This gives you a predictable, discoverable structure. When a developer needs a border color for an input on focus, they can guess the token name: color-border-input-focus.
- 1Global tokens describe the value: color-blue-500, space-4, radius-md
- 2Alias tokens describe the intent: color-primary, space-md, radius-default
- 3Component tokens describe the usage: button-bg, input-border-focus, card-shadow
- 4Never skip the alias tier — components should not reference global tokens directly
- 5Names follow CTI: category-type-item-state (e.g. color-bg-primary-hover)
Token Types
Your design system needs tokens for at least six categories. Let us walk through each one with real examples.
Color Tokens
Colors are the most complex token type because they need the most alias layers — background, foreground, border, interactive states, and semantic colors (success, warning, danger, info).
:root {
/* Global: raw palette */
--color-blue-50: oklch(0.97 0.02 250);
--color-blue-500: oklch(0.62 0.18 250);
--color-blue-600: oklch(0.55 0.18 250);
/* Alias: semantic */
--color-primary: var(--color-blue-600);
--color-bg-primary: var(--color-white);
--color-text-primary: var(--color-gray-900);
/* Component: scoped */
--button-bg: var(--color-primary);
--button-bg-hover: var(--color-blue-700);
}
Spacing Tokens
Use a consistent scale. The most common approach is a base-4 or base-8 system where every value is a multiple of 4px.
:root {
--space-0: 0;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
--space-8: 32px;
--space-12: 48px;
--space-16: 64px;
}
Typography Tokens
Typography needs size, weight, line-height, letter-spacing, and font-family tokens. Group them logically.
:root {
--font-family-sans: 'Inter', system-ui, sans-serif;
--font-family-mono: 'JetBrains Mono', monospace;
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
}
Shadow, Radius, and Motion Tokens
:root {
/* Shadows */
--shadow-sm: 0 1px 2px oklch(0 0 0 / 0.05);
--shadow-md: 0 4px 6px oklch(0 0 0 / 0.07);
--shadow-lg: 0 10px 15px oklch(0 0 0 / 0.1);
/* Radius */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-full: 9999px;
/* Motion */
--duration-fast: 100ms;
--duration-normal: 200ms;
--duration-slow: 300ms;
--easing-default: cubic-bezier(0.4, 0, 0.2, 1);
--easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
}
Why Tokens Matter: The Real-World Case
Let us walk through a concrete scenario. Your company has a web app, a mobile app, and a marketing site. Without tokens, each team hardcodes values:
With tokens, the brand refresh is a one-line change to the alias token. Every platform consumes the same token source and rebuilds.
A common trap is creating tokens for every unique value, including one-off spacing or colors. If a value appears in only one place and has no semantic meaning, it does not need a token. Over-tokenizing creates a bloated, hard-to-maintain system. A good rule of thumb: if you cannot explain why a value might need to change independently, it is not a token — it is a magic number you should probably align to an existing token instead.
Token Formats and Interoperability
The W3C Design Tokens Community Group (DTCG) is standardizing a JSON format for tokens. Here is what the spec looks like:
{
"color": {
"brand": {
"$type": "color",
"$value": "#6366f1"
}
},
"spacing": {
"md": {
"$type": "dimension",
"$value": "16px"
}
},
"typography": {
"body": {
"$type": "typography",
"$value": {
"fontFamily": "Inter",
"fontSize": "16px",
"fontWeight": 400,
"lineHeight": 1.5
}
}
}
}
The $type and $value prefixed with $ are DTCG conventions that separate metadata from token groups. This format is tool-agnostic — Style Dictionary, Figma Tokens, Cobalt UI, and others can all consume it.
| What developers do | What they should do |
|---|---|
| Using hex colors as global token names (--color-6366f1) Hex values in names are meaningless to humans and impossible to remember or guess | Use descriptive names (--color-indigo-500) |
| Components referencing global tokens directly (var(--color-blue-600)) Skipping the alias tier makes theming impossible — you cannot swap blue for purple without touching every component | Components reference alias tokens (var(--color-primary)) |
| Creating tokens for every unique value in Figma Over-tokenizing creates maintenance burden without benefit — aim for 80-150 tokens, not 500 | Only tokenize values that are reused or might change |
| Inconsistent naming across token types (--btn-bg vs --color-button-background) Inconsistent names make tokens undiscoverable — developers waste time searching instead of guessing correctly | Follow a single CTI convention everywhere |
Tokens in Multi-Platform Systems
Design tokens shine brightest in multi-platform organizations. You define tokens once in a platform-agnostic format (JSON), then transform them into:
- CSS custom properties for web
- Swift/Kotlin constants for native mobile
- XML resources for Android
- SCSS variables for legacy systems
- JavaScript objects for CSS-in-JS
Tools like Style Dictionary handle these transformations. The key insight: tokens are not a CSS feature. They are a design decision format that happens to output CSS (among other things). When you think of tokens as "CSS variables with extra steps," you are missing 80% of their value. They are the single source of truth that prevents design drift across every surface your brand touches.