Token Architecture
Tokens work in three tiers: raw values, the intent those values serve, and optionally the components that use them. Citations only ever point one tier down. That’s what makes a rebrand or dark mode a one-line change instead of a search through the whole codebase.
The problem
Section titled “The problem”If a button’s background is hardcoded, or points straight at color.blue.500, then a rebrand or dark mode means hunting down every place that value was used. If it points at color.action.primary instead, you change one semantic mapping and everything downstream follows. Brand alignment shows how this structure is where a real brand refresh meets the product system.
The model
Section titled “The model”The three tiers come from the token notes in Murphy Trueman’s design-system-ops toolkit (knowledge-notes/token-architecture.md).
Each tier only points at the tier directly inside it, and only the innermost one holds a real value.
---
config:
flowchart:
padding: 6
nodeSpacing: 20
---
flowchart TD
subgraph C["Component (optional)"]
CT["button.<br/>background.<br/>default"]
subgraph S["Semantic"]
ST["color.action.<br/>primary"]
subgraph P["Primitive"]
PT["color.blue.500"]
V(["Raw value<br/>#2563EB"])
end
end
end
CT --> ST --> PT --> V
style C fill:transparent,stroke-dasharray:5 4
Primitives
Section titled “Primitives”Raw values, named for what they are: color.blue.500. Nothing in a product should point at a primitive directly, except a semantic token.
Semantic tokens
Section titled “Semantic tokens”Intent: what a value is for, like color.action.primary or color.feedback.error. A semantic token gets its value by pointing at a primitive. This is the tier a theme or rebrand changes.
Component tokens
Section titled “Component tokens”An optional third tier that scopes semantic intent to one component, like button.background.default. It points at a semantic token, never at a primitive.
Here’s the same chain as token definitions, simplified to one line per token:
"color.blue.500": "#2563EB""color.action.primary": "{color.blue.500}""button.background.default": "{color.action.primary}"The curly braces mark an alias: a value that points at another token by name instead of holding a value of its own. Only the primitive holds a real value. Every other tier is a chain of aliases, and that chain is what the practices below protect. The real file format nests these names differently. See Store tokens in the shared DTCG format.
Practices
Section titled “Practices”Reference strictly downward
Section titled “Reference strictly downward”Citations flow component → semantic → primitive, never sideways and never skipping a tier. The design-system-ops notes call a skipped tier “the most architecturally damaging token violation.” button.background.default: {color.blue.500} appears to work, because the right color shows up. But “a rebrand or theme change that correctly updates the semantic tier will not reach this component.” It breaks silently, and you only find out mid-rebrand.
"button.background.default": "{color.blue.500}""button.background.default": "{color.action.primary}"Say the rebrand repoints color.action.primary to {color.purple.500}. The second button turns purple with the rest of the product. The first stays blue, because it never pointed at color.action.primary in the first place.
Name semantic tokens for intent, never appearance
Section titled “Name semantic tokens for intent, never appearance”The same notes: “A semantic token that describes visual appearance has failed its purpose. color.semantic.blue is a primitive with extra steps.” The moment the brand shifts to purple, color.semantic.blue is either a lie or a mass rename:
"color.semantic.blue": "{color.purple.500}""color.action.primary": "{color.purple.500}"Both tokens now hold the same purple. Only one name still tells the truth. Token naming covers how to structure the rest of the name.
Store tokens in the shared DTCG format
Section titled “Store tokens in the shared DTCG format”The Design Tokens Community Group (DTCG) format is now a shared standard. Its first stable release had editors from Figma, Adobe, Google, Microsoft, Meta, and others agreeing on one file format, which Style Dictionary, Tokens Studio, Terrazzo, and Figma can all read. That spec, DTCG 2025.10 (October 2025), defines 13 token types (color, dimension, fontFamily, and so on) and composite tokens like typography and shadow. In a composite token, each sub-value must itself reference tokens correctly, not just the top-level value. The spec also defines resolver files, which combine token sets into modes like light/dark or brand variants.
In a DTCG file, the dotted name becomes nested groups, and each token is an object with its value in $value and its type in $type:
{ "color": { "action": { "primary": { "$type": "color", "$value": "{color.blue.500}" } } }}The path through the groups (color → action → primary) is the token’s name, and the alias syntax is the same as in the simplified version above. Platform divergence shows a composite typography token whose sub-values are all aliases.
Keep translucent colors linked to their base
Section titled “Keep translucent colors linked to their base”A hover background or a scrim is usually an existing color at partial opacity. If that token stores its own semi-transparent value, it holds a raw value instead of an alias. A rebrand then misses it, just like a token that skips a tier. Until recently, the tools forced that copy. Tokens Studio’s modified colors (a Pro feature) can add alpha to a base color or mix two colors, but the plugin resolves them to hex on export because “Figma Variables does not support Modified Colors.” Figma’s September 2026 release closed that gap on the Figma side: opacity can now be “applied on top of any color without detaching,” and a number variable can supply the opacity.
The DTCG format has no single way to write this yet. A color value carries an optional alpha from 0 to 1, as Robson walks through. But a curly-brace alias “can ONLY target complete tokens,” per the 2025.10 format spec, so {color.gray.100} can’t also say “at 8%.” Until tools agree, the build step has to keep the link. GitButler found that its token build tool, Terrazzo, rejected Figma’s exports of these colors, so its build writes them out as CSS color-mix():
--bg-hover: color-mix( in srgb, var(--fill-gray-bg) var(--opacity-bg-hover), transparent);Both inputs stay variables, so changing either the base color or the opacity token updates the hover color.
Apply the opacity to an opaque base. In Figma, opacity on a color that is already translucent is ignored: “the source alpha wins,” according to a report on Figma’s plugin-typings repo. The Variables panel disables the field in that case, but the plugin API still stores the number. That hidden value takes effect later if someone removes the opacity upstream.
Keep platforms out of token names
Section titled “Keep platforms out of token names”Per the design-system-ops notes, platform differences (web pixels vs. iOS points, different typefaces) are handled by transformation tooling, never encoded in the name. Transformation tooling is software like Style Dictionary that converts one token file into each platform’s native format. So it’s spacing.4, not spacing.web.4. Platform divergence walks through that transformation step end to end. Specifications applies the same platform-neutral idea to whole components.
Treat token names as contracts
Section titled “Treat token names as contracts”Murphy Trueman argues that “your design system is already an API; the question is whether it’s a good one.” A token name isn’t a label. It’s a contract with every consumer, human or machine. Renaming one is a breaking change (Release management). Documentation for agents covers the machine consumers.
Common mistakes
Section titled “Common mistakes”- Adopting the DTCG format without checking tool support. A stable spec doesn’t mean every tool in your stack has caught up. Check that your version of Style Dictionary, Tokens Studio, or another pipeline tool actually supports 2025.10 before depending on it.
- Running a primitives-only system. Without a semantic tier, theming is impossible. Every value change means hunting down every primitive reference instead of repointing one alias.
- Letting token count grow faster than the product. That growth usually means one-off tokens are being created instead of existing intent being reused.
- Creating component tokens for every component up front. Curtis adds tokens gradually, naming them inside a component and promoting them to shared tokens only when other components need the same decision.
- Assuming a translucent alias survives export. Each tool stores “base color plus opacity” its own way. A step that doesn’t understand it either flattens it to a fixed color, as Tokens Studio does when it exports to Figma, or fails the build, as Terrazzo did for GitButler. Check the generated output, not just the source file.
- Keeping tokens nothing uses. Trueman’s token-audit skill flags tokens that no other token or component Citations. They clutter autocomplete and confuse the people choosing between them.