---
name: design-tokens-systems
description: Build scalable token architectures — primitives, semantics, component mappings — that make design systems consistent and themeable.
---

# Design Tokens & Systems

From raw values to semantic tokens to component APIs — the three-tier architecture

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.

```css
: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`, not `link-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.

```css
: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.

```css
: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:

```css
[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

1. Every `*-content` token passes 4.5:1 contrast against its parent.
2. Switching themes changes ONLY the semantic layer — primitives stay.
3. No raw hex/rgb values in component code — everything traces to a token.
4. Spacing values all come from the scale — no magic numbers.
5. Component tokens are the only thing a developer needs to theme a component.