Skip to content

What are design tokens?

The named values underneath a design system — what they are, how to tier and name them, and how to get them into Figma and code without keeping four copies in sync.

By Srinidhi BhatUpdated 9 min read

A design token is a named value used in place of a raw one. Instead of #4F46E5 you write action-primary; instead of 16px you write space-4. The name is the point: a value can change once and every place that referenced the name follows, in the design file and in the code at the same time.

That is the whole idea, and it is genuinely small. What makes design tokens worth an article is everything that follows from it — how you tier them, how you name them, and what happens to a system when those two decisions are made badly.

Why not just use hex codes?

Because a hex code cannot answer a question. #4F46E5 tells you a colour; it does not tell you whether this is the brand colour, the primary action, the focused-input ring, or a link — and those four things only happen to be the same colour today. The moment one of them needs to change, you are searching a codebase for a string and guessing which occurrences meant which thing.

A token records the intent alongside the value. That makes three things possible that raw values make impossible:

  • Theming. A dark mode is not a second set of components. It is the same components with a different value behind the same names.
  • Blast radius. “What breaks if this changes?” becomes a lookup of what references the token, rather than a grep and a hope.
  • One source, many outputs. The same token set can be written out as CSS custom properties, a Tailwind theme, a Figma variable collection and an iOS asset catalogue — all generated, none hand-kept.

The three tiers

Nearly every mature token system converges on the same three-tier shape. It is worth understanding as a chain of references rather than as three separate lists, because the references are what do the work.

1 · Primitives (the raw scale)
Every value the system is allowed to use, named after what it is: indigo-600, space-4, radius-md. Primitives carry no opinion about where they are used. A component should almost never reference one directly.
2 · Semantic roles (the decisions)
Names for jobs, pointing at primitives: action-primary → indigo-600, surface-raised → neutral-50, text-muted → neutral-500. This tier is where a theme lives: light and dark are two sets of answers to the same set of questions.
3 · Component tokens (the last mile)
Per-component names pointing at roles: button-bg → action-primary, card-border → border-subtle. This tier exists so one component can diverge without forking the role that every other component shares.

Skipping tier 2 is the most common and most expensive mistake. A system whose buttons reference indigo-600 directly has no dark mode and no rebrand — only a find-and-replace, performed by hand, forever.

The chain, in CSS custom properties
:root {
  /* 1 · primitive — a value, named for what it is */
  --indigo-600: #4f46e5;

  /* 2 · semantic role — a job, pointing at a primitive */
  --action-primary: var(--indigo-600);

  /* 3 · component token — one component's use of that job */
  --button-bg: var(--action-primary);
}

.dark {
  /* Only tier 2 is restated. Tiers 1 and 3 never move. */
  --action-primary: var(--indigo-400);
}

Naming that survives a redesign

Token names outlive the values behind them, so name the job rather than the appearance. The failure mode is famous enough to have a canonical example: a token called blue-button in a product that later turns green.

Instead ofName itBecause
blue-500action-primaryThe role survives a rebrand; the hue does not.
grey-lightsurface-sunkenIn dark mode, “light” is the darker of the two.
font-14text-body-smThe size can change; the step in the scale is stable.
margin-mediumspace-4“Medium” is relative to a scale nobody wrote down.

A naming pattern that scales

Most teams land on some version of category-concept-property-variant-state, dropping the parts that do not apply. Concretely:

Reading a token name
text-on-action-primary-hover
│    │  │      │       └─ state
│    │  │      └───────── variant
│    │  └──────────────── concept (the role it sits on)
│    └─────────────────── relationship
└──────────────────────── category (what property it sets)

Two rules matter more than the exact pattern. Be consistent about ordering, so names sort into meaningful groups. And never encode a value in a name — the moment space-16 means anything other than 16px, every reader of that name has been lied to.

Getting tokens into Figma and code

A token set that lives in one place and is retyped into another is not a token set — it is two documents that agree for a while. The three common approaches, honestly compared:

ApproachHow it worksWhere it breaks
Hand-keptThe Figma file and the CSS are edited separately by whoever notices.Immediately, and invisibly. Nothing can detect that they disagree.
Token file + build stepA JSON source of truth is transformed into each platform's format.Nothing keeps the JSON and the Figma file in step; that edge is still manual.
Generated from one modelEvery output — CSS, Tailwind, Figma variables, docs — is compiled from the same graph.Nothing, as long as no output is ever edited by hand downstream.

The third is what Arkitype does: the tiers above are the actual data model, and the Figma Variables bundle, the Tailwind config, the CSS custom properties and the documentation are all compiled from it. There is no copy to keep in sync because there is no copy.

Four mistakes that break a token system

  1. Too many primitives. A 900-token palette nobody can hold in their head gets bypassed. Ten steps per family is plenty; the constraint is the feature.
  2. Components referencing primitives. Covered above, and worth repeating: it is the single change that costs the most to undo later.
  3. Tokens with no accessibility contract. If text-on-surface and surface are independent decisions, someone will eventually pick a pair that fails contrast. Check the pairing, not the colour — see the accessible-palette guide.
  4. No enforcement. A token system with a hardcoded hex beside it will lose. Lint for raw values, and give any code-generating tool the token list — including AI coding tools, which invent values enthusiastically when handed none.

Where to start

You do not need the full three tiers on day one. The order that gets a usable system fastest:

  1. Pick one brand colour and generate a full ramp from it.
  2. Write the ten or so semantic roles you actually reference — surface, text, border, action, and their “on” pairs.
  3. Add a spacing scale and a type scale, both from one base and one ratio.
  4. Check every foreground-and-background pair for contrast, in both modes.
  5. Only then add component tokens, and only where a component actually diverges.

That is roughly the first ten minutes in Arkitype, which does steps 1 and 4 for you and will not let step 4 quietly fail. If you would rather do it by hand, the order still holds — it is the order the dependencies run in.

Build the system, not just the swatches

Arkitype does everything on this page from one brand colour — tiered tokens, semantic roles, contrast checked live against WCAG, and output for Figma, Tailwind, MUI and CSS that is all generated from the same source.

Keep reading