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.
On this page
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 writes them as JSON, with properties that start with a dollar sign so they cannot clash with group names.
{
"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, 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.
Example name
color.violet.700Value
#6d28d9Changes when
color.action.primaryValue
{color.violet.700}Changes when
color.text.defaultValue
{color.ink}Changes when
Example name
button.primary.backgroundValue
{color.action.primary}Changes when
{
"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}" }
}
}
}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.purpleused for buttonstext.dark, which is light in dark modespacing.16used as a layout ruleblue2,blueNew,blueFinalhero.gradient.lisanamed after a person
Names that last
color.action.primarycolor.text.defaultspace.sectionpointing at{space.16}- A numbered scale such as
blue.100toblue.900 gradient.herowith 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. 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.
: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;
}If you use Tailwind v4, the @theme block is itself a token layer: every --color-* variable becomes utilities. Tailwind colours 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.
@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;
}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.
- 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 covers the wider habit.
Questions people ask
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.
Written by Gradiently
The team behind Gradiently, a design tool built around Marks: living gradients that make everything you design look like yours.
See our profile