---
name: design-tokens
description: Authoring, naming, validating and compiling design tokens for the FunkyLime design system. Use this whenever work touches color, spacing, typography, radius, elevation, motion or z-index — including adding a token, renaming one, theming, dark mode, adding a new platform target, or writing any CSS/Tailwind/React Native style value. Also use when a screen needs a value that does not appear to exist yet, before inventing one.
---

# Design tokens

The token layer is the contract between design and code. If a value in a component
does not trace back to a token, the design system has a hole in it and nobody finds
out until three products have drifted apart.

This skill covers: what the tiers mean, how to name a token, when a new token is
allowed, and how tokens get compiled to platforms.

## The three tiers

Read `reference/token-grammar.md` for the full naming grammar and the current token
inventory. The short version:

| Tier | Example | Who references it |
|---|---|---|
| **Primitive** | `color.blue.600`, `space.4` | Semantic tokens only. Never a component, never a screen. |
| **Semantic** | `color.action.background.default` | Components and screens. This is the layer you normally use. |
| **Component** | `button.primary.background.rest` | The one component it names. Nothing else. |

The rule that matters: **a screen or component never references a primitive.** If you
find yourself writing `color.blue.600` in a component, the semantic token you actually
needed is either missing or you have not looked for it. Look first.

Primitives are the palette. Semantics are the decisions. A primitive rename is
cosmetic; a semantic rename is a design change.

## Adding a token

Before adding one, answer three questions in order:

1. **Does an existing semantic token already mean this?** Search
   `reference/token-grammar.md` for the intent, not the value. Two tokens with the
   same hex are fine if they mean different things (`color.border.subtle` and
   `color.divider.default` may both be `#E4E4E7` today and diverge in dark mode).
   Two tokens with the same *meaning* are a defect.
2. **Is this a one-off?** A value used once in one screen is not a token. Note it as a
   deliberate exception in the component's usage docs and move on. Tokens are for
   decisions that repeat.
3. **Which tier?** If a second component would plausibly use it, it is semantic. If
   only ever one component, it is component-tier and lives under that component's
   namespace.

Then author it in DTCG shape in `tokens/`, run the validator, and open a PR. Never
edit generated output in `build/` — it is overwritten on every build.

## DTCG format

Source tokens use the W3C Design Tokens Community Group format: every token is an
object with `$value` and `$type`, aliases reference other tokens in curly braces.

```json
{
  "color": {
    "action": {
      "background": {
        "default": {
          "$value": "{color.blue.600}",
          "$type": "color",
          "$description": "Primary interactive surface. Pairs with color.action.foreground.default at 4.5:1 minimum."
        }
      }
    }
  }
}
```

`$description` is not optional on semantic tokens. It is what tells the next person —
or the next agent — whether this token is the right one. Write it as intent plus
constraint, as above. "Blue background" is a useless description; "primary
interactive surface, pairs with X" is a usable one.

## Validate before compiling

```bash
node scripts/validate-tokens.mjs tokens/
```

The validator fails the build on:

- a token missing `$type` or `$description`
- an alias pointing at a token that does not exist
- a **component or semantic token holding a raw value** where an alias was expected
- an **orphan primitive** — defined but referenced by nothing (dead weight, or a sign
  someone forgot to wire it up)
- a semantic token whose foreground/background pairing falls below WCAG 2.2 AA
  contrast, where the pairing is declared in `$extensions.pairs`

The contrast check is the one worth keeping even when it is annoying. Catching a
failing pair at token-authoring time costs a minute; catching it at design review
costs a redesign, and catching it after launch costs an accessibility audit.

## Compiling to platforms

`config/style-dictionary.config.js` compiles the DTCG source to four targets:

| Target | Output | Consumed by |
|---|---|---|
| Web CSS | `build/css/tokens.css` (custom properties, `:root` + `[data-theme="dark"]`) | React app |
| Tailwind | `build/tailwind/theme.js` | Tailwind config `theme.extend` |
| React Native | `build/rn/tokens.ts` (typed object) | mobile app |
| Docs | `build/docs/tokens.json` (flat, with descriptions) | Storybook token page |

```bash
npx style-dictionary build --config config/style-dictionary.config.js
```

Tailwind gets the *semantic* layer only. Exposing primitives to Tailwind reintroduces
`bg-blue-600` in the markup, which is exactly the drift the token layer exists to
prevent.

## Theming

Dark mode is a **value swap on semantic tokens**, never a new set of semantic names.
`color.action.background.default` exists once and resolves differently per theme. If
you find yourself writing `color.action.background.default.dark`, stop — the theme
belongs in the mode dimension, not the token name.

Same rule for density and brand: modes, not names.

## Renaming and removing

A semantic token rename is a breaking change to every consumer. The sequence is:

1. Add the new name as an alias to the old value.
2. Mark the old token `$extensions.deprecated` with a reason and a target release.
3. Migrate consumers.
4. Remove the old token in the next minor release, not the same one.

Never rename and remove in a single PR, even when the token looks unused. The
validator only sees this repository; a token can be consumed by a product you cannot
grep.

## When you cannot find the right token

Stop and say so. Do not pick the visually closest primitive and move on — that is how
a system quietly acquires seven greys. Report which decision you were trying to
express and what you looked for. A missing token is useful information about the
system; a silently hardcoded hex is not.
