The Architecture of Semantic Tokens: Moving Beyond Color Palettes
From a colour value to an interface decision: a real token alias, a worked role mapping, and a migration checklist.
Published Updated
A colour tells you what a control looks like. A token should also help you understand why that colour belongs there—and what must stay true when the control changes state. The distinction becomes useful when one value serves several jobs that may later diverge.
Start with the consumer
Consider the original type tester on this portfolio. Its selected weight button needs a surface, readable text, a boundary, and a recognisable selected state. The range control needs an accent. Those needs happen to share a colour; they are not the same responsibility.
Scroll sideways or focus the table and use arrow keys →
| Layer | Current mapping | Consumer |
|---|---|---|
| Value and role | #ff4b32 → --proof-accent | The Design realm’s action accent. |
| Compatibility alias | --pd-gold: var(--proof-accent) | The type tester retains its older variable name. |
| Component state | .ts__weight.is-active | Uses that alias for its surface and border; text uses the ink role. |
| Native control | accent-color: var(--pd-gold) | The size and tracking range inputs. |
These mappings describe the current website, not a claimed client token export.
The name gold is now misleading: the resolved colour is orange-red. Keeping the alias lets an existing component consume the newer theme without replacing every reference at once. It is a migration boundary, not an ideal naming convention.
Separate a value from its job
A useful architecture separates primitives from roles, then connects roles to actual component properties. The following worked example makes that separation explicit. It is deliberately smaller than a complete design system.
.example {
--neutral-950: #080807;
--neutral-50: #f1ede4;
--action-surface: var(--neutral-950);
--action-text: var(--neutral-50);
--focus-ring: var(--neutral-950);
}
.example[data-theme="dark"] {
--action-surface: var(--neutral-50);
--action-text: var(--neutral-950);
--focus-ring: var(--neutral-50);
}
.example__action {
background: var(--action-surface);
color: var(--action-text);
}
.example__action:hover {
text-decoration: underline;
}
.example__action:focus-visible {
outline: 2px solid var(--focus-ring);
outline-offset: 3px;
}Surface and text change together. Hover adds emphasis without silently replacing the foreground; focus remains a separate visible treatment.
CSS custom properties follow the cascade and normally inherit. Where a value is declared therefore matters as much as its name. Here, the role overrides live on the themed wrapper; the action consumes them beneath it. MDN explains custom-property scope and inheritance.
Name the decision, then test its combinations
A naming pattern is useful when it answers a real question. action-surfacedescribes a job; neutral-950 describes a palette position. A component-specific alias is worthwhile when that component genuinely needs independent change—not merely because another nesting level looks architectural.
States also combine. In the portfolio tester, the general hover rule changes text colour, while a selected button already has an accent-filled background. A more specific selected-and-hovered rule retains ink text. Reviewing “hover” and “selected” separately would miss that intersection.
Test default, hover, focus, and selected-plus-hover against their actual surroundings. Check text and non-text contrast, keyboard focus, and forced-colour behaviour. A semantic name expresses intent; it does not certify accessibility.
Migrate the contract, not just the spelling
- Inventory the consumers before changing a value or removing an alias.
- Document the role, supported states, and cases where it should not be used.
- Keep a compatibility alias while consumers move to the new name.
- Review representative components in each theme, including combined states.
- Remove the old alias only after its remaining uses are accounted for.
Interchange formats help tools exchange these decisions. The Design Tokens Community Group’s 2025.10 format defines typed values and references; it is a Community Group specification, not a W3C Standard. Export tooling still needs integration and release checks. An alias does not make every downstream surface update safely or instantly.
For the broader system context, see the Oasis design-system case study. For the website example itself, inspect the live type controls.