How Developers Actually Consume Tokens
Developers consume design tokens as generated views of one file: CSS custom properties for plain stylesheets, a Tailwind theme whose utility classes are generated from the token names, and Figma Variables carrying the same names on the design side. The handoff holds when that file is treated as an interface between design and engineering — names are the API, values are the implementation.
Most writing about tokens sits in the design chair: what to name, how to layer, what to export. This piece sits in the developer’s chair instead — what actually arrives when a token file lands in a repository, what changes in daily work, and what a developer is entitled to push back on. The fundamentals live in our design tokens guide.
What formats do tokens arrive in?
Rarely as the source file alone. The source is tool-neutral JSON; consumers get generated views of it, and three of those cover most web work.
CSS custom properties — the zero-tooling path. The generated stylesheet
declares one property per token, and components consume them with var():
:root {
--color-accent: oklch(0.546 0.215 262.9); /* #2563eb */
--space-3: 16px;
}
.button {
background: var(--color-accent);
padding-inline: var(--space-3);
}
No build step, no framework — a <link> tag is the whole integration, which
is why this path exists in nearly every token setup regardless of what else
does.
Tailwind v4 @theme — utilities generated from token names. Tailwind v4
derives its utility classes from theme variables, so the token file becomes
the utility vocabulary:
@theme {
--color-brand-600: oklch(0.546 0.215 262.9);
--spacing-3: 16px;
}
bg-brand-600 now exists as a class because the token does. The utility
set is a projection of the token names, not a parallel list someone has to
keep in sync.
Figma Variables — the same names on the designer’s side. One line, because the point is the names rather than the mechanics: what the designer binds a fill to and what the developer types into a stylesheet resolve to the same entry.
Scale Composer generates all of these from the same source — one canonical
DTCG file, with CSS custom properties,
a Tailwind v4 @theme block and Figma
Variables exported beside it, hex carried alongside the OKLCH values.
Open the developer-facing exports in Scale Composer — the same
token names rendered as each consumer format, side by side.

What changes in day-to-day work when tokens arrive?
Three shifts, roughly in the order they appear.
Reviews argue about roles instead of values. “That should be
text-secondary” is an argument someone can win — the role either fits the
intent or it doesn’t. “That gray looks wrong” is a matter of eyesight and
monitor calibration. Watching review comments move from values to names is
one of the clearest signals the handoff is working.
New screens compose instead of invent. A developer building a screen
picks from existing names — background, text-primary, space-5 — the way
they pick from a library API, rather than receiving a fresh set of hex codes
with each mockup.
Hand-copied values die out. The old failure mode — copying #2563eb from
an inspector panel and typing #2564ec on a tired Friday — loses its habitat:
values enter through the file or not at all.
The intuitive why: a name carries intent across the handoff, and a raw value
strips it. A reviewer can check text-secondary against what the text is
for; no reviewer can recover what #64748b was meant to be from the
number alone.
Why treat the token file as an interface?
Because two teams depend on it from opposite sides — the situation interface contracts exist for, the same discipline software applies at any API boundary. Concretely:
- Names are the API. A rename is a breaking change: every
var(--color-accent)and every Figma binding is a caller. Renames deserve a deprecation window — the old name kept as an alias pointing at the new one — not a silent swap. - Values are the implementation. They may change freely; that is the
point of the arrangement. A retune that changes what
brand-600stores should ship without any consumer editing a line. - Additions are non-breaking. A new token can land at any time; nothing consumes it yet.
- Removals need migration. A token deleted while callers still reference it fails like any removed endpoint — at the call sites, at the worst time.
What should a developer push back on?
Three things, each a contract violation wearing spec clothing.
Raw values in specs. A mockup annotated “use #2563eb” deserves the
question which token is that? If the answer is brand-600, the spec should
say so. If there is no answer, the next item applies.
One-off values with no token home. A value that exists at exactly one call site is a decision nobody recorded. The push-back is not “no” — the design may well be right — it is “this lands in the token file first, then I consume it from there.”
References to tokens that don’t exist yet. A spec naming text-muted
when the file holds no such role is a call against an unshipped endpoint.
Define it first — a one-line, non-breaking addition by the rules above — then
build against it.
What does the same spec produce in review?
A card component, specced twice.
Value-style: Title 20px in #1e293b, 24px below the image, on a #f8fafc
card. The implementing PR reproduces four raw values, and review has to
reverse-engineer each one: is 20px text-lg or a new size? Is #1e293b our
primary text color or a near miss? Is 24 a spacing step or a guess? Four
archaeology questions per component, typically answered by guessing.
Token-style: Title text-lg in text-primary, space-5 below the image,
on a surface card. The PR writes itself:
.card-title {
font-size: var(--text-lg);
color: var(--color-text-primary);
margin-top: var(--space-5);
}
Review now has one question per line — is this the right role for the
intent? — which is a design question a reviewer can actually answer. The
diff also reads well from the future: when text-lg is retuned next year,
this component follows without appearing in any diff at all.
Check the contract on your own handoff
The interface framing turns handoff quality into two testable questions: could a value change without any consumer edit, and would a rename break callers you can list? Open a token set and check both from the consuming side — pick a role, see the name repeated across every export format, then change its value and watch the API hold while the implementation moves.