A brand blue changes. In a healthy design system, nobody opens five codebases and pastes a new hex code. One value changes, and the web, iOS and Android apps all pick it up on their next build.
That works because the blue isn't really a colour in any of those codebases. It's a name that points at a name that points at a colour. Try it below.
A token is a named decision
A design token is a name for a design decision, stored as data instead of written into code. #2563eb is a value. color.bg.accent is a decision: this is the colour for things that need attention.
Most mature systems split those names into three tiers. Material 3 calls them reference, system and component tokens. Other teams say primitive, semantic and component. The idea is the same.
- Primitive tokens are the raw palette, like
color.blue-500. They say what a colour is, not what it's for. Nothing in a product should use them directly. - Semantic tokens say what a value is for, like
color.bg.accentorcolor.text.default. This is the layer designers mostly think in. - Component tokens are scoped to one component, like
button.primary.bg. They let a button change without changing every accent on screen.
Each tier is an alias of the one above. button.primary.bg holds {color.bg.accent}, which holds {color.blue-500}, which holds the hex. Resolving a token means following the chain until you reach a real value.
That's why one edit reaches so far. Change color.blue-500 and everything that points at it, directly or indirectly, gets the new value. In the demo, that's one semantic token and one component token. In a real system, it's often hundreds.
Themes swap the middle layer
Dark mode is where the tiers pay off.
Switch the demo to Dark. Four semantic tokens re-point to different primitives. color.bg.accent now points at blue-400, because a lighter blue reads better on a dark surface. The component tokens don't move at all. button.primary.bg still says {color.bg.accent}, and it's still right.
The same trick covers a second brand, a high-contrast theme or a denser layout. Each one is a different set of references for the semantic layer. Components are written once, against semantic names, and never learn which theme they're in.
That's the real job of the semantic tier. If components pointed straight at primitives, dark mode would mean editing every component. With a semantic layer in between, it means editing one mapping.
In Figma, this is what variable modes are. A collection of semantic variables has a Light column and a Dark column, and each cell can alias a primitive variable.
The build step
Tokens live as data, so a build tool has to turn them into something each platform can use. The common pipeline looks like this.
- Source Figma variables or token JSON
- Parse and resolve Follow aliases, apply mode
- Transform Per platform: names, units, colour format
- Format CSS, Swift, Kotlin, XML, JS
The best-known tool for this is Style Dictionary. It started at Amazon and was released on GitHub in 2017, and it describes itself as forward-compatible with the W3C token format.
The source format has a standard now. The W3C Design Tokens Community Group published the first stable version of its specification, 2025.10, in October 2025. A token is an object with a $value. A $type says what kind of value it is. An alias is the target's path in curly braces.
{
"color": {
"$type": "color",
"blue-500": {
"$value": {
"colorSpace": "srgb",
"components": [0.145, 0.388, 0.922],
"hex": "#2563eb"
}
},
"bg": {
"accent": { "$value": "{color.blue-500}" }
}
}
}
Notice the colour isn't a hex string. In the 2025.10 colour module, it's a colour space plus channel values, with hex as an optional fallback. That leaves room for Display P3 and OKLCH colours, which a hex code can't describe.
Themes live in a separate part, the Resolver module. It describes sets of tokens and modifiers like theme: light | dark, and a tool picks one context per modifier to produce a final set.
Here's that output, built live from the first demo's state.
What the transforms actually do
Every platform wants the same decision spelled differently.
- Names. CSS wants
--color-bg-accent. Swift and Kotlin wantcolorBgAccent. A transform rewrites the token path into each platform's naming rules. - Colours. CSS takes hex. UIKit wants
UIColor(red:green:blue:alpha:)with channels from 0 to 1. Compose wantsColor(0xFF2563EB), with alpha first. Android XML wants#AARRGGBB, also alpha first, so an 8-digit CSS hex pasted straight into Android gets its alpha in the wrong place. - Units. A CSS pixel, an iOS point and an Android dp are all density-independent units, so 8px becomes 8pt and
8.dpwithout changing the number. The maths only appears when the source is in rem. Style Dictionary'ssize/remToDpmultiplies by a base font size, 16 by default. - References. CSS can keep aliases as
var()references, so the browser can switch theme at runtime. Swift and Kotlin usually get the final values, resolved per mode at build time, because a native constant can't point at a CSS variable.
Look at the CSS tab. Dark and Brand B only override four lines, all in the semantic layer. That's the three tiers showing up in the output.
Where the truth lives in 2026
For years, the honest answer was "in Figma, and also in a JSON file someone exports from Figma". The pipeline started with a plugin or a script that read Figma's Variables REST API, which needs a full seat on an Enterprise plan.
Design data now flows into code through more direct paths.
- Code Connect links a Figma component to the real component in your codebase, so Dev Mode shows your actual code instead of generated CSS.
- Figma's MCP server lets a coding agent read a selection. One tool,
get_variable_defs, returns the variables and styles used in it. If a designer typed a raw hex instead of using a variable, there's no variable to return, and the agent is left guessing. - Code layers were announced at Config 2026. A design layer can become an interactive code layer on the canvas, with early access through a waitlist from July.
- Figma Make on your local code connects Make to a GitHub repository in the Figma desktop app. Each prompt becomes a local commit on a branch, and you open a pull request for review. It's a closed beta, and pull requests are GitHub only for now.
So Figma can now write to the repo, as well as be read from it. That's useful, and it makes the question of which side holds the truth harder to dodge.
What breaks
Two things break token pipelines, and neither is technical.
The first is naming drift. Figma calls it bg/accent, the JSON calls it color.bg.accent, and someone adds color.background.accent in code because they didn't find the first one. Now there are two tokens for one decision, and they'll drift apart the first time someone edits only one.
The second is two sources of truth. If designers edit variables in Figma and engineers edit the JSON, every sync is a merge, and the tool has to guess which side wins. Pick one direction. Either Figma is the source and the JSON is generated, or the repo is the source and Figma is updated from it. A build that fails when they differ is better than a sync that quietly picks one.
A token system is mostly a naming system with a compiler attached. The compiler is the easy part.