The three token tiers
Primitive, semantic and component tokens — why only one tier holds raw values, and the naming grammar that makes the system machine-checkable.
FDS organizes tokens into three tiers. The rule that makes re-theming cheap: raw values (hex colors, px sizes) exist ONLY in the primitive tier — everything above is an alias.
| Tier | CSS prefix | Example id | Role |
|---|---|---|---|
Primitive (ref) | --fx-ref-* | ref.brand.600 | Raw scales — the only tier with literals |
| Semantic | --fx-* | color.primary, space.4 | Intent-named aliases — what you should use |
| Component | --fx-c-* | c.button.radius | Narrow aliases for one component's knobs |
Why aliases all the way up
A theme (or a brand change) never edits a component: it re-points aliases. Dark mode re-points color.bg from ref.neutral.0 to ref.neutral.900; a brand swap re-points color.primary to a new primitive. Every component that reads var(--fx-color-primary) updates for free.
Components read semantic tokens, never primitives. If a button hardcoded ref.brand.600 it would survive a theme swap unchanged — which is exactly the bug this architecture prevents.
Naming grammar
Every token has a dot-path id whose segments are lowercase kebab-case, and a deterministic CSS variable: color.on-primary → --fx-color-on-primary. The mapping is pure (tokenIdToCssVar), so tooling never needs a lookup table.
import { tokenIdToCssVar, hasToken, tokenType, getToken } from 'flexa-design-system';
tokenIdToCssVar('space.4'); // '--fx-space-4'
hasToken('color.primary'); // true — the vocabulary gate
tokenType('shadow.md'); // 'shadow' (DTCG $type)
getToken('color.primary'); // full registry entry (tier, refs, value)The `on-X` convention
Every filled color X ships a paired on-X for the text/icon that sits on it: color.primary / color.on-primary, color.danger / color.on-danger. These pairs are contrast-checked in CI (WCAG 2.2 AA) in both light and dark — see the contrast matrix on the Tokens page.
Reserved namespaces
The first segment of every token id (color, space, radius, ref, c, …) is a reserved namespace. isTokenNamespace(segment) is the discriminator validation tools use: a dot-path inside a reserved namespace that is NOT a known token is an off-system token (a typo or an unregistered custom id) and gets rejected, while img.png or 1.5px are ordinary CSS literals and pass through untouched.