Short answer
In layers. A base layer names every raw colour once (blue-600); a semantic layer gives each a job (text-primary, surface-raised, border-danger) by referring to a base token; and components use only semantic tokens. Themes, dark mode and brands then swap what semantic tokens point to without touching component code. Store colours with an explicit colour space, and treat the Design Tokens Community Group format as a community specification rather than a W3C standard.
A design token is a named value — a colour, a size, a duration — stored once and consumed by design tools and code. For colour, the name matters more than the value. If a button's background is written as #1A73E8 in forty components, a rebrand is forty edits and a dark mode is forty conditionals. If it is written as a token whose value can change, it is one edit. The Design Tokens Community Group's format describes a token as an object with a $value and a $type, grouped into nested groups, where one token can alias another with a {group.token} reference. That aliasing is what makes layered colour systems possible: a semantic token holds no colour of its own, only a pointer to a base colour.
Most mature systems settle on three layers. Base (or primitive, or palette) tokens are the raw scale — every step of every hue — and they are named for what they look like, such as blue-600. They change rarely, when the palette itself is redesigned. Semantic (or alias, or system) tokens are named for purpose — text-secondary, surface-sunken, border-focus, status-danger-background — and point at base tokens; they change per theme and per mode. Component tokens (button-primary-background) are optional and point at semantic tokens; they exist so one component can diverge without redefining a semantic role for everyone. Apple's guidance takes the same view from the platform side: its system colours are defined by purpose rather than by appearance, and apps should not hard-code the values because they change between releases.
A useful test: if a token's name would still be true after a rebrand, it belongs in the semantic layer. 'brand-blue' fails the test; 'action-primary' passes.
A token that holds '#1A73E8' is implicitly sRGB. That is fine until a system wants wide-gamut colours, or wants to generate tints in a perceptual space. The Design Tokens Community Group's Color Module (2025.10, a Final Community Group Report) stores a colour as a colour-space name plus an array of components and an optional hex fallback, and lists fourteen spaces including srgb, display-p3, oklch and lab. Recording the space explicitly means a pipeline can emit oklch() for browsers that support it, a hex fallback for those that do not, and the right native value for iOS and Android, from one source. It also records intent: a colour authored in OKLCH says the designer chose lightness and chroma deliberately.
The most common failure is semantic tokens that are really base tokens with new names — 'primary-light', 'primary-dark' — which say nothing about use and therefore cannot be remapped safely for dark mode. The second is too many semantic tokens: a separate token for every component's every state, so the semantic layer becomes as large as the component layer and nobody can find the right one. The third is contrast being checked on base colours rather than on semantic pairs. What must pass WCAG is text-primary on surface-default, not blue-600 in isolation, so contrast checks belong at the level where foreground and background tokens meet, for every theme and mode.
| Layer | Named for | Example | Points to | Changes when |
|---|---|---|---|---|
| Base / primitive | Appearance | blue-600 | A literal colour value | The palette is redesigned |
| Semantic / alias | Purpose | text-link, surface-raised | A base token | Theme, mode or brand changes |
| Component | One component's part | button-primary-background | A semantic token | One component diverges |
Why: Components use base tokens (or hex values) directly.
Fix: Route every component colour through a semantic token and remap those per mode.
Why: The semantic layer has grown a token per component state.
Fix: Keep semantic tokens for shared roles and move one-offs to component tokens.
Why: Contrast was checked on the palette, not on the pairs the theme creates.
Fix: Test every foreground/background semantic pair in every theme automatically.
Each statement is labelled by kind — established fact, a standard’s requirement, observed market data, a convention, or Colourwise’s own interpretation or analysis — with the strength of the evidence behind it.
ConventionModerate evidence
The Design Tokens Community Group format describes tokens as objects with $value and $type properties, organised into groups, and allows one token to reference another using {group.token} syntax.
Caveat: The format module is a community-group draft and is still changing.
FactModerate evidence
The Design Tokens Color Module 2025.10 is a Final Community Group Report that stores a colour as a named colour space, a components array and an optional six-digit hex fallback, supporting fourteen colour spaces including srgb, display-p3, lab and oklch.
Caveat: A community group report is not a W3C Recommendation; tool support varies.
ConventionModerate evidence
Apple's Human Interface Guidelines describe system colours as defined by purpose rather than appearance and advise against hard-coding their values, which can change between releases.
Colourwise interpretationModerate evidence
Contrast requirements apply to foreground–background pairs as rendered, so a token system is most reliably checked at the semantic layer, where text, icon and border tokens meet surface tokens, in every theme.
Based on: Follows from WCAG 2.2 defining contrast between text or components and their adjacent colours, not for colours in isolation.
Reviewed 29 September 2026. Colourwise summarises its sources in its own words and does not reproduce standards text or proprietary colour data. Spotted an error? Tell us.