// 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.

ADVANCED · 17 MIN READ · DESIGN SYSTEMS

§ 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.

The three-tier token chain A blueprint diagram showing a single value flowing through three tiers: a primitive color token, a semantic token that references it, and a component token that references the semantic tier in turn. PRIMITIVE raw value · no meaning blue.500 → #2563eb SEMANTIC role · aliases primitive color.action.primary → {blue.500} COMPONENT scoped · aliases semantic button.primary.background → {color.action.primary} rendered A rebrand repoints blue.500 — every layer below it updates without being touched directly.
FIG. 01 — One value, three tiers. Each tier references exactly the tier below it — never skips one, never reaches sideways.

§ 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.

The token transform pipeline A blueprint diagram showing a single DTCG-format source file passing through a transform step and fanning out into four platform-specific outputs: CSS, iOS, Android, and Figma Variables. tokens.json DTCG format single source transform resolves aliases CSS custom properties iOS — Swift / .xcassets Android — XML resources Figma Variables (via API)
FIG. 02 — One resolved source, four native outputs. Nothing downstream is hand-maintained, so nothing downstream can drift.

§ 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/primary and a code token called color.action.primary look 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

/ token_tier_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.

DESIGN SYSTEMS DESIGN TOKENS DTCG THEMING GOVERNANCE