# Design tokens: one source of truth for colour and type

[Canonical HTML page](https://gradiently.design/guide/design-tokens)

A brand colour that lives in forty files will drift. A token lives in one place and every platform reads from it. Here is how to structure design tokens, name them so they last, and ship them to code and design tools.

## The short version

- Design tokens are named, platform neutral values for design decisions such as colours, font families, sizes, spacing, radii, shadows and motion timings.
- Most token systems use three tiers: primitive tokens hold raw values, semantic tokens describe a purpose, and component tokens apply purposes to specific parts.
- The W3C Design Tokens Community Group format stores tokens as JSON with $value and $type properties and references other tokens with curly braces.
- Tools such as Style Dictionary transform one token file into CSS custom properties, JavaScript, iOS and Android resources.
- Dark mode and multiple brands become a matter of swapping the values behind semantic tokens, while components stay unchanged.

**Design tokens** are named values for every design decision a brand repeats: colours, typefaces, type sizes, spacing, corner radii, shadows and animation timings. Instead of typing `#5b21b6` into a stylesheet, a Figma file and an iOS app, you define `color.brand.700` once, in one file, and generate each platform's format from it. Change the token and every product updates together. That single source of truth is the whole point.

## What a design token looks like

A token has a name, a value and a type. The format published by the [W3C Design Tokens Community Group](https://www.w3.org/community/design-tokens/) writes them as JSON, with properties that start with a dollar sign so they cannot clash with group names.

```json
{
  "color": {
    "violet": {
      "100": { "$type": "color", "$value": "#ede9fe" },
      "500": { "$type": "color", "$value": "#8b5cf6" },
      "700": { "$type": "color", "$value": "#6d28d9" },
      "900": { "$type": "color", "$value": "#4c1d95" }
    },
    "ink": { "$type": "color", "$value": "#14121f" },
    "paper": { "$type": "color", "$value": "#faf8ff" }
  },
  "font": {
    "heading": { "$type": "fontFamily", "$value": ["Fraunces", "Georgia", "serif"] },
    "body": { "$type": "fontFamily", "$value": ["Inter", "system-ui", "sans-serif"] }
  },
  "radius": {
    "control": { "$type": "dimension", "$value": { "value": 10, "unit": "px" } }
  },
  "duration": {
    "quick": { "$type": "duration", "$value": { "value": 160, "unit": "ms" } }
  }
}
```

Primitive tokens in the community group format. Groups nest freely; the path, such as color.violet.700, becomes the token's name.

- color.violet.100: #ede9fe
- color.violet.500: #8b5cf6
- color.violet.700: #6d28d9
- color.violet.900: #4c1d95
- color.ink: #14121f
- color.paper: #faf8ff

The colour tokens above as swatches. These are raw ingredients: nothing yet says which one is a button or a heading.

## Primitive, semantic and component tokens

A flat list of colours is a palette, not a system. The structure that holds up over years has three tiers, each referring to the one below. Components never touch raw values; they ask for a purpose.

| Tier | Example name | Value | Changes when |
| --- | --- | --- | --- |
| Primitive | `color.violet.700` | `#6d28d9` | The palette is redesigned |
| Semantic | `color.action.primary` | `{color.violet.700}` | A role moves to another colour |
| Semantic | `color.text.default` | `{color.ink}` | Dark mode, a new brand |
| Component | `button.primary.background` | `{color.action.primary}` | One component needs an exception |

References in curly braces are aliases. Change color.violet.700 and every token that points at it follows.

```json
{
  "color": {
    "action": {
      "primary": { "$type": "color", "$value": "{color.violet.700}" },
      "primary-hover": { "$type": "color", "$value": "{color.violet.900}" }
    },
    "text": {
      "default": { "$type": "color", "$value": "{color.ink}" },
      "on-action": { "$type": "color", "$value": "{color.paper}" }
    },
    "surface": {
      "page": { "$type": "color", "$value": "{color.paper}" },
      "tint": { "$type": "color", "$value": "{color.violet.100}" }
    }
  }
}
```

Semantic tokens describe what a colour is for. This is the layer designers and engineers should talk in.

Many teams stop at two tiers, primitive and semantic, and add component tokens only where a component genuinely differs. That keeps the file small enough to understand.

## Naming design tokens so they last

Names outlive values. A token called `color.purple` breaks the day the brand turns teal; a token called `color.action.primary` survives it. Name semantic tokens by role, and keep primitive names descriptive of the value.

### Names that age badly

- `color.purple` used for buttons
- `text.dark`, which is light in dark mode
- `spacing.16` used as a layout rule
- `blue2`, `blueNew`, `blueFinal`
- `hero.gradient.lisa` named after a person

### Names that last

- `color.action.primary`
- `color.text.default`
- `space.section` pointing at `{space.16}`
- A numbered scale such as `blue.100` to `blue.900`
- `gradient.hero` with its stops as tokens

Pick a pattern, such as category, then role, then variant, then state, and write it down in your [brand style guide](https://gradiently.design/guide/brand-style-guide). Consistency in naming matters more than which pattern you choose.

## From token file to CSS and apps

The token file is not shipped as it is. A build step transforms it into whatever each platform needs. Style Dictionary is the most widely used open source tool for this, and others read the same format. For the web, the output is usually CSS custom properties.

```css
:root {
  --color-violet-100: #ede9fe;
  --color-violet-700: #6d28d9;
  --color-violet-900: #4c1d95;
  --color-ink: #14121f;
  --color-paper: #faf8ff;

  --color-action-primary: var(--color-violet-700);
  --color-text-default: var(--color-ink);
  --color-surface-page: var(--color-paper);

  --font-heading: Fraunces, Georgia, serif;
  --radius-control: 10px;
  --duration-quick: 160ms;
}

.button {
  background: var(--color-action-primary);
  color: var(--color-paper);
  border-radius: var(--radius-control);
  transition: background var(--duration-quick) ease;
}
```

Generated output. Aliases become var() references, so the semantic layer survives into the browser. See [CSS variables for theming](https://gradiently.design/guide/css-custom-properties-theming).

If you use Tailwind v4, the `@theme` block is itself a token layer: every `--color-*` variable becomes utilities. [Tailwind colours](https://gradiently.design/guide/tailwind-colors) shows how to wire a token ramp into it. The same file can also produce Swift constants and Android resources, which is where tokens pay for themselves on multi platform teams.

## Dark mode and multiple brands

Because components only read semantic tokens, a theme is just a different set of values for that layer. Dark mode redefines `color.text.default` and `color.surface.page`; a second brand redefines `color.action.primary`. Nothing in the components changes.

```css
@media (prefers-color-scheme: dark) {
  :root {
    --color-text-default: #e9e5f5;
    --color-surface-page: #0b0a14;
    --color-action-primary: var(--color-violet-500);
  }
}

[data-brand="harbour"] {
  --color-action-primary: #0f766e;
}
```

Two overrides, no component edits. Gradients deserve their own dark values too; [dark mode gradients](https://gradiently.design/guide/dark-mode-gradients) explains how to tune them.

> **Tokens in design tools** Figma Variables support collections and modes, which map neatly onto primitive and semantic tokens and light and dark themes. Keep the token file as the source and sync into the design tool, not the other way round, or the two will disagree within a month.

## Rolling out design tokens

1. **Audit what exists** Collect every colour, font size and spacing value in use. Expect duplicates that differ by one hex digit.
2. **Define primitives** Reduce them to clear scales. Building colour ramps in OKLCH keeps steps even; see [OKLCH explained](https://gradiently.design/guide/oklch-explained).
3. **Add the semantic layer** Name roles: text, surface, border, action, feedback. Point each at a primitive.
4. **Automate the build** Generate CSS and app resources from the file in your build, never by hand.
5. **Guard it** Lint for raw hex values in components, so new code uses tokens from day one.

Tokens keep the product consistent, but brands also drift in the graphics made outside it: posts, slides, banners. A Gradiently brand kit holds the same decisions, your colour palettes, heading and body fonts, logos and voice, so designs made in the Studio match. And because Gradiently works inside ChatGPT, Claude and other assistants that support remote MCP servers, an assistant with a scoped API key can create on brand designs from the same workspace. [Brand consistency](https://gradiently.design/guide/brand-consistency) covers the wider habit.

## FAQ

### What are design tokens?

Named values for design decisions such as colours, fonts, spacing, radii and motion, stored in one platform neutral file and transformed into CSS, app code and design tool variables.

### What is the difference between primitive and semantic tokens?

Primitive tokens hold raw values, like `color.violet.700`. Semantic tokens describe a purpose, like `color.action.primary`, and point at a primitive.

### Are design tokens the same as CSS variables?

No. Tokens are the source, stored in a neutral format such as JSON; CSS custom properties are one output generated from them, alongside outputs for other platforms.

### Is there a standard format for design tokens?

The W3C Design Tokens Community Group publishes a JSON format using $value, $type and curly brace aliases, which tools such as Style Dictionary can read.

### How do design tokens handle dark mode?

Components read semantic tokens, and a dark theme supplies different values for those tokens, so components need no changes.
