// design_systems · token_architecture
Design Tokens Are a Naming System, Not a Style Guide
Most teams "adopt design tokens" by renaming hex codes into variables and calling the job finished. The naming architecture underneath — tiers, aliasing, and who's allowed to add what — is the part that actually decides whether the system survives a rebrand, a new platform, or three years of new hires who never read the original spec.
§ 01
What a Token Actually Is
A design token is not a CSS variable with better branding. A custom property, a Swift constant, and an Android XML resource are three different outputs of a token — one export format each. The token itself is the source data one level up: something meant to survive translation into all three at once, plus whatever platform gets added next year.
Three parts make something a token rather than just a renamed constant:
Name — the stable identifier design and code both
point to, e.g. color.background.action.primary. This
is the part that's supposed to never change, even when the value
behind it does.
Value — a literal, or, past the first tier, a reference to another token's name instead of a literal at all. Section three covers why that distinction is the whole point.
Type — what kind of value this is: color,
dimension, duration, cubic-bezier, font family, and so on. The
type is what tells a build step whether 8 means 8px,
8 seconds, or a stroke weight, and it's the piece teams skip most
often because a spreadsheet of hex codes doesn't obviously need
one.
Leave the type out and the file still looks organized — it's just not machine-readable in the way that matters. The moment you try to auto-generate outputs for more than one platform from it, everything downstream has to guess.
§ 02
Three Tiers, One Rule Each
This is the piece most "getting started with tokens" articles skip, and it's the one that determines whether the system holds up. Tokens live in three tiers, each answering a different question, each with exactly one job:
Primitive (also called "reference" or "global")
tokens hold raw values with no opinion about where they're used —
blue.500: #2563eb, space.4: 16px. A
primitive doesn't know it's a button color or a card gap. It's a
palette entry, full stop.
Semantic tokens hold meaning — what a value is
for — and they get there by referencing a primitive
rather than holding a literal of their own:
color.action.primary: {blue.500}. This is the tier a
rebrand actually touches: repoint the reference and every semantic
token's meaning stays intact while its value changes underneath
it.
Component tokens are scoped to one component and
reference a semantic token in turn:
button.primary.background: {color.action.primary}.
Not every component needs its own token — most should resolve
straight to a semantic value — but for the handful with genuinely
component-specific rules (a button's pressed-state darkening,
say), this tier is where that rule lives instead of leaking into
the semantic layer.
§ 03
The Aliasing Chain
"Aliasing" is the mechanism that makes the three tiers actually
work: a token's value can be a reference to another token's name —
commonly written {color.action.primary} — instead of
a literal. Resolving a token means walking that chain until you
hit a tier with a real literal at the bottom, which is always the
primitive tier by construction.
The rule worth enforcing, even before any tooling exists to enforce it: a token should reference exactly one tier below it, never skip one, and never reach sideways. A component token that points straight at a primitive — skipping semantic entirely — has quietly opted out of theming for itself; when the brand color changes, every component that took the shortcut has to be found and edited individually, which defeats the reason the semantic tier exists.
Depth is worth watching too. A chain of one or two hops resolves instantly and stays debuggable — open a component token, see what it aliases, see what that aliases, done. Chains longer than that, or ones that loop back on themselves, turn a five-second lookup into archaeology, and most token tooling will simply refuse to resolve a genuine cycle rather than guess. If a token needs a third layer of indirection to express, that's usually a sign a tier is missing or misused, not a sign the chain should get longer.
§ 04
Naming Conventions That Survive Scale
A naming structure that works at 40 tokens and a naming structure that works at 4,000 are different problems. The formula that scales, used with minor variations across most mature token systems, orders segments from broadest to most specific:
category . concept . property . variant . state
For example: color.background.action.primary.hover —
category color, concept background, property
action, variant primary, state hover.
Broad-to-narrow ordering matters more than it looks like it
should, because it's what makes autocomplete and alphabetical
token-picker lists usable once there are hundreds of entries —
everything in one family sorts together instead of scattering
across the list by whatever word happened to come first.
The rule that matters most, and the one teams break first under
deadline pressure:
never name a semantic or component token after its
value.
color.brand.red is a primitive name wearing a
semantic token's clothes — it's fine at the primitive tier, where
"red" genuinely describes the value, but disastrous one tier up,
where it's supposed to describe a role. The first time
the brand color changes to anything that isn't red, every consumer
of color.brand.red is now displaying a token whose
own name is factually wrong, and nobody renames a token that's
referenced in four hundred places without a very good reason to.
Name semantic and component tokens for the job they do —
color.action.primary, not
color.brand.red — and the rebrand becomes a value
change instead of a search-and-replace across the whole codebase.
§ 05
The W3C DTCG Format
For years, every token tool shipped its own JSON shape, which meant moving from one to another meant rewriting the whole file. The Design Tokens Community Group (DTCG), working under the W3C, has been converging the industry on one shared shape — it's worth being precise that this is a community group draft, not a finished W3C Recommendation, but it's already the format Figma Variables, Tokens Studio, and most build tooling are aligning toward as a common interchange target.
The shape is simple: every reserved property is prefixed with
$ so it can never collide with a token or group name,
and plain, non-$ keys form the nesting structure
itself — a group is just an object, and a token inside it declares
$value and $type:
{
"color": {
"blue": {
"500": { "$value": "#2563eb", "$type": "color" }
}
},
"action": {
"primary": {
"$value": "{color.blue.500}",
"$type": "color",
"$description": "Default background for primary actions."
}
},
"button": {
"primary": {
"background": {
"$value": "{action.primary}",
"$type": "color"
}
}
}
}
Three things worth noticing in that shape:
$value can be a literal ("#2563eb") or
an alias ("{color.blue.500}") — the curly-brace
syntax is the reference mechanism from section three, spelled out
as a dot path through the JSON tree exactly as nested.
$type can be set once on a group and inherited by
everything under it, so a whole "color" subtree doesn't need to
repeat "$type": "color" on every leaf. And composite
types — shadow, border, gradient, typography, cubic-bezier — hold
structured objects instead of a single value, so a "heading"
typography token can bundle font family, size, weight, and
line-height as one resolvable unit instead of four
separately-aliased tokens that have to be kept in sync by hand.
§ 06
Transforms: One Source, Many Platforms
The entire point of a shared, structured source format is that it can be fed through a build step once and produce every platform's native format as output, instead of a human copying values into four codebases by hand and hoping they stay in sync.
A transform tool — Style Dictionary is the tool most teams reach
for, though the category isn't limited to it — walks the token
tree, resolves every alias down to its literal, and emits one file
per platform in that platform's native syntax: CSS custom
properties for the web, a Swift or
.xcassets file for iOS, XML color and dimen resources
for Android, and, increasingly, a direct push into Figma's own
Variables via its API so the design tool and the codebase are
reading from the identical resolved values rather than two
hand-maintained copies of the same idea.
This is the part that makes the tier structure from section two non-optional in practice, not just in theory: a transform step can only regenerate every platform correctly if the source file is unambiguous about what aliases what. Hardcode a value at the component tier instead of aliasing it, and that value stops being part of the system the transform can see — it'll render correctly today and silently diverge from the token it should have referenced the next time that token's value changes.
§ 07
Theming Without Duplicating Every Token
Dark mode is the case that proves whether the tier structure from
section two was worth the trouble. Done right, the semantic and
component tiers never learn that a "mode" exists at all —
color.background.primary is the same token name
whether the product is in light or dark mode; only which primitive
it resolves to changes between the two. A component built once
against {color.background.primary} renders correctly
in both modes automatically, because the component was never told
which mode it's in — it just asked for "the primary background,"
and the answer changed underneath it.
The alternative — a separate, fully duplicated token set per mode, with component code branching on a theme flag to pick which set to read from — is the pattern teams back into when tokens were bolted onto an existing codebase after the fact rather than designed with theming in mind from the start. It works, but every new token has to be added twice, correctly, in sync, forever.
One consequence worth planning for rather than discovering later: theming makes contrast checking combinatorial, not additive. A brand with two modes and two color pairings doesn't have twice the contrast pairs to verify — every semantic foreground/background combination needs checking independently per mode, since a pairing that clears WCAG AA in light mode can fail outright once its two primitives are swapped for their dark-mode equivalents. The math from color contrast systems still applies here — it just needs re-running for every mode a token pair can resolve into, not once for the palette as a whole.
§ 08
Governance: Who Adds a Token
A token file with no review gate degrades the same way an
unmoderated shared color palette always has: someone needs a blue
that's almost the existing blue, doesn't find it in a
list of 40 near-identical entries, and adds
blue.510 rather than asking. Multiply that by a few
dozen contributors over a couple of years and the primitive tier
stops being a curated palette and turns back into the hex-code
sprawl tokens were supposed to replace.
The gate that scales without becoming a bottleneck is tier- dependent, not blanket:
Primitive changes or additions go through the design system team specifically — this is the smallest, slowest-moving tier, and it should stay that way on purpose.
Semantic tokens can be proposed by any team building a feature, but a proposal has to resolve to an existing primitive, and the first review question is always "does an existing semantic token already cover this," not "does this specific value exist yet." Most proposed new semantic tokens turn out to be an existing one under a different name.
Component tokens should be rare enough that adding one is a deliberate, justified exception — most component-level needs should resolve to a semantic token directly, with no component-specific token at all. If a team finds itself adding component tokens routinely, that's usually a sign the semantic tier is missing a role, not a sign the component tier needs to grow.
§ 09
Five Pitfalls That Look Like Tooling Bugs
-
01
"Fixing" dark mode by editing a component token directly. The dark-mode value looks wrong, so the quickest fix is to override the component token for that one case. It works today and quietly breaks the point of section seven — the override doesn't move when the semantic token is corrected properly, and now two places need to be fixed instead of one. Trace the value back to the semantic alias and fix it there.
-
02
Reviewing color tokens carefully and everything else barely. Spacing, radius, and duration tokens get far less scrutiny than color in most teams' review process, and audits consistently find that's exactly where drift accumulates first — a dozen near-identical spacing values doing the job one token should. Apply the same primitive-tier discipline to every token type, not just the palette.
-
03
Deleting a token instead of deprecating it. Removing an old token the moment its replacement ships breaks every consumer simultaneously, with no warning and no migration window. Alias the old name to the new value first, flag it deprecated, give consumers a release cycle or two to move over, and only remove it once nothing references the old name anymore.
-
04
Letting the primitive tier grow without a search step. Section eight's governance gate only works if it's actually applied — the common failure mode is a primitive list nobody checks before adding to it, which is how a palette ends up with four blues that are visually indistinguishable but numerically distinct. A two-minute "does this already exist" search before a new primitive gets added prevents most of the sprawl.
-
05
Assuming a matching name means a matching source. A Figma Variable called
action/primaryand a code token calledcolor.action.primarylook like the same thing, but unless both are generated from the one DTCG source file in section five, nothing guarantees they resolve to the same value — they can drift silently, and usually do, the first time one side gets updated without the other. Shared naming isn't shared truth; a shared source file is.
§ 10
Quick Reference
| Tier / concept | Holds | Who edits it |
|---|---|---|
| Primitive | Raw value, no meaning attached | Design system team only |
| Semantic | A role, aliasing a primitive | Any team, via reviewed proposal |
| Component | A scoped exception, aliasing a semantic token | Rarely — most needs stop at semantic |
| Alias syntax | A reference to another token's name | {group.token.name} |
$type |
Declares the value kind for the transform step | color, dimension, duration, cubicBezier… |
| Retiring a token | Alias old name to new value, deprecate, then remove | Never delete outright |
None of this is really about tooling. Style Dictionary, Tokens Studio, and Figma Variables all change shape every year or two — the naming architecture underneath is the part built to outlast whichever tool happens to read it today. Get the tiers, the aliasing discipline, and the governance gate right, and a token system stays legible to someone who joins the team three years from now with no memory of why any of it was set up this way. Skip them, and "we use design tokens" just means the hardcoded values moved one file over.