Design Tokens & Systems
Build scalable token architectures — primitives, semantics, component mappings — that make design systems consistent and themeable.
Build scalable design token architectures that make design systems consistent, themeable, and maintainable across platforms.
The Three-Tier Architecture
Tier 1: Primitives
Raw, named values. No meaning attached — just the palette of available options.
:root {
/* Color primitives */
--blue-50: oklch(0.97 0.02 240);
--blue-100: oklch(0.93 0.04 240);
--blue-500: oklch(0.55 0.2 240);
--blue-900: oklch(0.2 0.1 240);
/* Spacing primitives */
--space-0: 0rem;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-4: 1rem;
--space-8: 2rem;
/* Type scale primitives */
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
}
Rules:
- Name by value, not usage:
blue-500, notlink-color. - Use OKLCH for perceptual uniformity — equal steps feel equal.
- Spacing: 4px base unit, multiples of 4 (4/8/12/16/24/32/48/64).
- Include negative space in the scale for offsets.
Tier 2: Semantics
Purpose-driven aliases that reference primitives. This is where meaning lives.
:root {
/* Foreground hierarchy */
--fg-primary: var(--gray-900);
--fg-secondary: var(--gray-600);
--fg-tertiary: var(--gray-400);
--fg-muted: var(--gray-300);
/* Background elevation */
--bg-base: var(--gray-50);
--bg-raised: var(--white);
--bg-sunken: var(--gray-100);
/* Border progression */
--border-subtle: var(--gray-200);
--border-default: var(--gray-300);
--border-strong: var(--gray-400);
/* Brand */
--color-primary: var(--blue-500);
--color-primary-hover: var(--blue-600);
--color-primary-content: var(--white);
/* Semantic feedback */
--color-success: var(--green-500);
--color-warning: var(--amber-500);
--color-error: var(--red-500);
--color-info: var(--blue-500);
}
Rules:
- Every semantic token references a primitive — never a raw value.
- Name by purpose:
fg-primary,bg-raised,border-subtle. - Group by role: foreground, background, border, brand, semantic.
- Dark theme: swap the semantic layer, keep primitives.
Tier 3: Component Mappings
Component-specific tokens that reference semantics.
:root {
--btn-bg: var(--color-primary);
--btn-fg: var(--color-primary-content);
--btn-border: var(--color-primary);
--btn-radius: var(--radius-md);
--btn-padding-x: var(--space-4);
--btn-padding-y: var(--space-2);
--card-bg: var(--bg-raised);
--card-border: var(--border-subtle);
--card-radius: var(--radius-lg);
--card-shadow: var(--shadow-sm);
}
Rules:
- Component tokens reference semantics, never primitives.
- One token per styleable property that varies between components.
- Component tokens are the API surface — changing them themes every instance.
Dark Theme Strategy
Dark themes swap the semantic layer only:
[data-theme="dark"] {
--fg-primary: var(--gray-100);
--fg-secondary: var(--gray-400);
--bg-base: var(--gray-900);
--bg-raised: var(--gray-800);
--border-subtle: var(--gray-700);
--color-primary: var(--blue-400);
}
For PageWeave/daisyUI: use update_theme(website, config: { css }) with bare variables (no [data-theme] wrapper — PageWeave adds it). Dark themes MUST include color-scheme: dark;.
Token Naming Conventions
| Pattern | Example | When |
|---|---|---|
{category}-{level} |
fg-primary |
General hierarchy |
{component}-{property} |
btn-bg |
Component-specific |
{category}-{state} |
btn-bg-hover |
Interactive states |
{size}-{scale} |
text-sm, space-4 |
Scales |
Avoid: color1, blue-light-ish, spacing-medium. If you can't describe the token's purpose in one word, rename it.
Spacing System
Pick a base unit. Stick to multiples. Random values signal no system.
Base: 4px (0.25rem)
Scale: 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64
Rem: 0, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 6, 8, 12, 16
Use the scale for: padding, margin, gap, border-radius (separate scale), component sizing.
Typography Scale
Use a modular scale for type sizes:
Minor third (1.2): 0.694, 0.833, 1, 1.2, 1.44, 1.728, 2.074, 2.488rem
Major third (1.25): 0.64, 0.8, 1, 1.25, 1.563, 1.953, 2.441rem
Pair with line-height and letter-spacing:
- Body: 1rem / 1.5 line-height / 0 letter-spacing
- Headings: larger size / 1.1–1.2 line-height / -0.02em letter-spacing
- Small text: 0.875rem / 1.4 line-height / 0.01em letter-spacing
- Data/mono: 0.875rem / 1.6 line-height / tabular-nums
Radius & Elevation Scales
Radius (separate from spacing — different rhythm):
--radius-none: 0
--radius-sm: 0.25rem (4px)
--radius-md: 0.5rem (8px)
--radius-lg: 0.75rem (12px)
--radius-xl: 1rem (16px)
--radius-full: 9999px
Elevation (choose ONE approach):
- Borders only (flat, editorial)
- Subtle shadows (modern SaaS)
- Layered shadows (Material)
- Surface tints (dark-mode-friendly)
Don't mix approaches. Commit.
Verification
- Every
*-contenttoken passes 4.5:1 contrast against its parent. - Switching themes changes ONLY the semantic layer — primitives stay.
- No raw hex/rgb values in component code — everything traces to a token.
- Spacing values all come from the scale — no magic numbers.
- Component tokens are the only thing a developer needs to theme a component.