# Design Tokens

> Build a small token system for a page or a pack - colour roles, spacing and type scales, radii, elevation, and chart-specific tokens (categorical series, sequential ramps, semantic up/down) so every artifact reads as one system instead of a series of one-offs. Trigger on "design system", "tokens", "CSS variables", "palette", "colour scheme", "consistent styling", "series colours", "make these pages match".

- Skill: `lukehle/design-tokens` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lukehle/design-tokens`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lukehle/design-tokens/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: Lukehle (https://skillmd.com/u/lukehle)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lukehle/design-tokens

---


# Design tokens

A token system is what stops every page being a fresh set of arbitrary decisions. It is also what
makes theming, printing, and accessibility tractable — you fix a value once instead of hunting
hex codes.

Keep it small. A page needs perhaps 30 tokens, not 300.

---

## Define by role, never by appearance

The most important rule. Name what a token is *for*, not what it looks like.

```css
/* WRONG - the name lies the moment you add a dark theme */
--light-grey: #e5e7eb;
--blue-500:   #3b82f6;

/* RIGHT - the role holds in every theme */
--surface:        #ffffff;
--surface-raised: #f7f8fa;
--border:         #e3e6ea;
--ink:            #14161a;   /* primary text     */
--ink-muted:      #5b6472;   /* secondary text   */
--ink-subtle:     #98a2b3;   /* tertiary, ticks  */
--accent:         #2f5fd8;
--accent-ink:     #ffffff;   /* text ON accent   */
```

`--ink-muted` still means "secondary text" in dark mode. `--light-grey` becomes a lie.

---

## The full set

```css
:root {
  /* surfaces + text */
  --surface: #fff;  --surface-raised: #f7f8fa;  --surface-sunken: #eef1f5;
  --border: #e3e6ea;  --border-strong: #c8cdd6;
  --ink: #14161a;  --ink-muted: #5b6472;  --ink-subtle: #98a2b3;

  /* accent + semantics */
  --accent: #2f5fd8;  --accent-ink: #fff;  --accent-wash: #eaf0fd;
  --positive: #1f7a4d;  --negative: #b3261e;  --warning: #9a6700;
  --positive-wash: #e8f4ee;  --negative-wash: #fbeceb;

  /* spacing */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 24px; --space-6: 32px; --space-7: 48px; --space-8: 64px;

  /* type */
  --text-xs: 11px; --text-sm: 13px; --text-base: 15px;
  --text-lg: 18px; --text-xl: 24px; --text-2xl: 36px;
  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* shape */
  --radius-sm: 3px; --radius-md: 6px; --radius-lg: 10px;
  --elev-1: 0 1px 2px rgb(0 0 0 / .06);
  --elev-2: 0 4px 12px rgb(0 0 0 / .10);
}
```

Three radii, two elevations. If you find yourself wanting a fourth radius, you probably want one of
the existing three.

---

## Semantic direction is not red/green

Finance UIs default to red-down / green-green. Two problems: it fails for the most common colour
vision deficiency, and **up is not always good** — a rise in churn, DSO, or burn is bad.

So:

- Encode direction with a **glyph** (▲ ▼) or the sign, always. Colour is reinforcement.
- Choose colour by **whether it is good**, not by direction — and let the caller decide which.
  `--positive` / `--negative` are about goodness; the arrow is about direction.
- Prefer a blue/orange pair over red/green where the semantics allow it; it survives every common
  form of colour blindness.

```html
<span class="delta bad">▲ 4.2 pts</span>   <!-- churn rose: up, and bad -->
```

---

## Chart tokens

Charts need their own tokens, and they should not be the UI accent colours reused at random.

```css
:root {
  /* categorical - ordered by how often they get used */
  --series-1: #2f5fd8;  --series-2: #d9770a;  --series-3: #1f7a4d;
  --series-4: #7a3fbf;  --series-5: #b3261e;  --series-6: #0e7490;

  /* sequential ramp - for heatmaps, 5 steps is usually enough */
  --seq-1: #eef3fb; --seq-2: #cfdcf6; --seq-3: #9db8ec;
  --seq-4: #5c86dd; --seq-5: #2f5fd8;

  /* diverging - only where a meaningful midpoint exists (variance vs plan) */
  --div-neg: #b3261e; --div-mid: #f3f4f6; --div-pos: #1f7a4d;

  /* chart furniture */
  --grid: #eceff3;  --axis: #c8cdd6;  --tick: var(--ink-subtle);
  --bar-neutral: #98a2b3;   /* anchors in a bridge */
}
```

Rules that matter more than the exact hues:

- **Categorical for unordered categories only.** Using a categorical palette for an ordered thing
  (aging buckets, cohort age) discards the ordering the reader needs.
- **Sequential for magnitude, diverging only with a real midpoint.** Variance versus plan has one:
  zero. Revenue does not.
- **Cap categorical at ~6.** Beyond that, group into "other" or switch to small multiples — nobody
  matches an eighth colour to a legend.
- **The same segment gets the same colour on every chart in the page.** Assign a colour per entity
  once, globally, not per chart.

Verify every pair against `artifact-accessibility` for contrast and greyscale separation. A palette
that only works in colour is half a palette.

---

## Theming

Define the **complete** light palette on bare `:root`, then redefine only the tokens that change:

```css
:root { /* full light palette - every token defined here */ }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --surface: #101215; --ink: #e8eaed; /* … */ }
}

:root[data-theme="dark"] { --surface: #101215; --ink: #e8eaed; /* … */ }
```

**Never give a colour its only definition inside a media query or a `[data-theme]` block.** That is
the bug that produces an unstyled page in the third theme state. See `artifact-theming` for why
there are three states and how to verify all of them.

---

## Using them

Reference tokens; never hardcode a value in a component.

```css
.kpi        { padding: var(--space-4); background: var(--surface-raised);
              border-radius: var(--radius-md); }
.kpi__label { font-size: var(--text-sm); color: var(--ink-muted); }
.kpi__value { font-size: var(--text-2xl); color: var(--ink);
              font-variant-numeric: tabular-nums; line-height: 1.1; }
```

A hardcoded hex or px inside a component is a token you have not named yet. Either add it to the
scale or use the nearest existing step — almost always the latter.

---

## Reuse across a set of pages

When several artifacts belong together, keep the token block byte-identical across them. Paste the
same `:root` into each page (there is no shared stylesheet under a strict CSP — see
`artifact-architecture`). Consistency here is what makes a set of pages read as one product rather
than as several unrelated tools.

---

## Related skills

- `artifact-theming` — the three-state theme mechanics these tokens serve
- `visual-hierarchy`, `data-typography` — the scales in use
- `artifact-accessibility` — contrast and greyscale validation
- `ui-antipatterns` — what defaulting these produces

