Skip to main content

Token Model

The token model defines the architectural contract of the system.

It establishes where values live, where meaning lives, what components may consume, and how the system evolves without semantic drift.

This document defines global invariants. Family docs define family-specific grammars and rules.


Core Principle

Separate value from meaning.

  • Core tokens define raw, themeable values
  • Semantic tokens define stable design meaning
  • Components and patterns consume semantic tokens only

Semantic tokens are the public API of the system.

The flow in one picture

raw value core token semantic token component
───────── ────────── ────────────── ─────────
"#020617" ──► core.colors.neutral.1000 ──► semantic.colors.action <Button />
.primary.background.default

only in theme never consumed by the contract surface consumes semantic
sources / files components components depend on tokens only
  • Left to right — values become meaning, meaning becomes UI.
  • Right to left — a theme change (light → dark, brand A → brand B) touches only the semantic → core arrow; the component does not change.
  • Each layer has one job and is not allowed to do the next one's job. This is what the invariants below enforce.

Architecture

core.{family} → semantic.{family} (foundation families: colors, font, spacing, sizing, radii, border, opacity, motion, z-index, elevation)
core.dataviz → semantic.dataviz (extension: analytical visualization)
semantic.* → components
components → patterns
patterns → applications

core.{family} is shorthand for the foundation families in ThemeTokens.core. There is no foundation key in the type contract — each family is a sibling at that level.

Layer roles

  • Core stores values only
  • Semantic stores meaning only
  • Components consume semantic tokens
  • Patterns compose components without bypassing the token model
  • Applications consume components, patterns, and — under strict rules — semantic tokens directly

Application consumption of semantic tokens

Applications may consume semantic tokens directly only in these cases:

  • App-level layout composition — page-level spacing, gutters, viewport sizing
  • Content composition — text styles for app-owned content outside component boundaries
  • One-off surfaces — unique screens or compositions that do not warrant a component
  • Platform integration — when no semantic component exists for the integration point

Applications must never:

  • consume core tokens directly
  • create parallel semantic vocabulary (new tokens duplicating existing meaning)
  • override component-level token contracts via direct semantic token consumption
  • use semantic tokens as a shortcut to bypass existing components or patterns

If an application repeatedly consumes the same semantic tokens in the same pattern, that pattern should become a component or a pattern — not remain as application-level token usage.


Semantic Color Grammar — FSL Projection

The semantic color token grammar {ux}.{role}.{dimension}.{state} is a formal FSL Structural Language §17.1 projection that renames and subsets FSL dimensions. The mapping is normative:

Token grammar axisFSL dimensionNotes
uxEntity KindProjection-scoped subset of FSL Entity Kinds: action, input, navigation, feedback, informational. Three kinds project to informational (Collection, Overlay, Structure — all presentational by FSL §1); Selection projects to input; Disclosure projects to navigation (structural orientation, ADR-001); Action/Input/Navigation/Feedback project 1:1.
roleEvaluationProjection-scoped name for the FSL Evaluation dimension. Values are identical (primary, secondary, accent, muted, positive, caution, negative).
dimension— (projection-defined)Projection-level vocabulary naming the colored part: background, border, text. These are not FSL Structural Role terms (Lexicon §2 defines part topology like root, control, label); the axis is introduced by this projection per FSL §17, scoped to color application.
stateStateValues identical, no renaming.

For the full Entity Kind → UX context mapping (covering all nine FSL Entity Kinds), see the Colors family.


Invariants

These rules apply to all token families.

1. Core is value-only

Core tokens define raw values, scales, ramps, and other themeable primitives.

Core must not encode:

  • UI intent
  • component names
  • modes
  • implementation-specific meaning

Naming distinction: "UI intent" means names that are component-specific or context-specific (core.border.width.button, core.color.card-background). It does not prohibit canonical use-site names in a small, closed scale when those names encode a constraint that an ordinal scheme cannot (e.g., core.border.width.selected encodes selected > default in the name itself). See borders.md for the canonical example.

2. Semantic is meaning-only

Semantic tokens define stable design intent.

Semantic tokens:

  • reference core tokens only
  • remain stable across themes and modes
  • form the public API consumed by UI code

Semantic→semantic alias exception (registered). Five cross-cutting tokens reference the semantic layer instead of core: semantic.focus.ring.color, semantic.consequence.destructive.ink (ADR-025), and semantic.valence.{positive,caution,negative}.ink (ADR-029). All are system-wide defaults that shadow a per-context token, and the alias exists so mode remaps carry them automatically — a core reference would freeze them in the base mode's value while the token they shadow moved. The alias is one level deep and typed (TokenRef<semantic.*>); adding a member to this class requires the §6 cross-cutting gate.

3. Core is never consumed directly by UI code

Core tokens exist for:

  • theme definition
  • token composition
  • controlled system evolution

They are not the API for components or product UI.

Exception: Infrastructure-only families (see invariant 7) export values directly without a semantic layer. Their tokens are consumed by layout systems and applications, not by the semantic mapping pipeline.

4. Meaning must remain stable

  • Themes may change core values and semantic mappings.
  • Modes do not change values. They remap semantic tokens to different core tokens within the same theme.
  • If meaning changes, create a new semantic token and deprecate the old one.

5. Names must express meaning, not appearance

Semantic names describe intent, role, context, dimension, state, or analytical function.

Do not name semantics by:

  • hue
  • raw style
  • component
  • mode
  • chart type
  • library behavior

6. No parallel vocabulary

Do not introduce new tokens that duplicate existing meaning.

Prefer reuse before creation.

Cross-cutting tokens are a registered class, not exceptions. Every grammar has a residual: meanings that cross its axes. A token is not parallel vocabulary when it answers a question that the principal grammar cannot ask in a single token — typically a system-wide concern that no {ux} owns. Cross-cutting tokens live as siblings of semantic.colors.*, never inside it, and coexist with per-context counterparts ({ux}.{role}.border.focused, etc.): per-context tokens answer "how does this {ux} vary?", cross-cutting tokens answer "what is the system default?".

Cross-cutting registry (complete as of this writing):

TokenQuestion the grammar cannot askADR
semantic.focus.ring.colorsystem focus indicator when no {ux} appliesADR-025
semantic.overlay.scrimdimming of obscured content behind a blocking layer
semantic.overlay.outlineboundary of a surface that occludes content — occlusion is neither role nor state, and it crosses UX contextsADR-027
semantic.consequence.destructive.inkforeground of a destructive part that paints no surface — the grammar cannot combine valence with emphasisADR-025
semantic.valence.{positive,caution,negative}.inkforeground of a part that reports a valence while painting no surface — generalizes the consequence-ink argumentADR-029
semantic.rail.trackunfilled part of a ProgressBar/Meter/Slider track — crosses Feedback and Input, darkens in dark mode while borders lightenADR-028

Adding a cross-cutting token requires the same gate as a RawValue exception (§8): technical necessity, JSDoc on the token, and registration in this table.

7. Families own their grammar, not their architecture

Each family may define its own semantic grammar.

Examples:

  • colors may use ux.role.dimension.state
  • typography may use text.family.step
  • radii may use radii.contract
  • dataviz may use analytical roles

This is valid.

What must remain constant is the architecture:

  • core = value
  • semantic = meaning
  • semantic = public API

Infrastructure-only families. Some families do not express durable UI meaning and therefore do not define a semantic layer. They export values directly as adaptation infrastructure — for example, breakpoints define viewport thresholds consumed by layout systems, not semantic intent consumed by components. This is architecturally valid when the family serves as operational infrastructure rather than design meaning.

8. RawValue exceptions are rare, intentional, and registered

Semantic tokens must reference core tokens. A RawValue is permitted only when a TokenRef is technically impossible (e.g., clamp() expressions mixing units from multiple token paths, or CSS units with no core token equivalent such as ch).

A bare constant is never a lawful RawValue. Every entry below is a composition — a clamp(), an rgba(), a unit with no core home — because a composition is the only thing a single {token.path} genuinely cannot express. A plain value is the opposite case: it becomes a TokenRef the moment core holds it, and core is the layer whose job is holding values (§1). So "core has no step for this value" is a missing core token, never a necessity — the fix is to add the step to core and reference it, and core.spacing.fixed.* (the non-fluid spacing scale, added for exactly this reason) is the precedent. semantic.spacing.inset.control.* shipped as a literal 6px under a necessity argument of that circular shape and was corrected in ADR-023; the guard that enforces this now has no exception list.

Approval criteria — a semantic RawValue must satisfy all three:

  1. Technical necessity: the value cannot be expressed as a single {token.path} reference.
  2. Local justification: a JSDoc comment on the token explains the necessity.
  3. Audit registration: the token is listed in the inventory below.

Approved RawValue inventory (complete list as of this writing):

Token pathReason
semantic.spacing.gutter.pageclamp() expression embedding multiple token refs — no single TokenRef can express responsive fluid gutters
semantic.spacing.gutter.sectionsame as above
semantic.spacing.separation.interactive.minclamp() with mixed units (px + token ref) — minimum touch-target separation cannot be expressed as a pure token reference
semantic.spacing.inset.action.blockclamp() with mixed units — the command trigger's block padding is a bounded 8–9px range the engine's unit steps straddle (ADR-021 addendum; this entry was added late — the token shipped unregistered, against this section's own rule)
semantic.sizing.measure.readingch units — character-based measure has no core token equivalent
semantic.overlay.scrimrgba() composing {semantic.opacity.scrim} — no single TokenRef can express a partial-opacity overlay color

Any new RawValue in the semantic layer requires an entry in this table before merging.

CSS-coupled core tokens

A separate, narrower exception applies to the core layer: a small set of core tokens carry CSS-only constructs (clamp(), container-query units, or in-string var(--tt-…) self-references) because their behaviour is only meaningful through the CSS cascade. toCssVars emits the necessary viewport fallbacks and media-query overrides for these tokens; consumers reading them via useResolvedTokens will see the unprocessed expression.

Two patterns appear in this category:

  1. Fluid primitivesclamp() + cqi expressions that need @supports (width: 1cqi) gating with viewport fallbacks emitted by toCssVars (extractContainerQueryVars + toViewportFallback).
  2. Cascade-preserving aliases — values that reference their own group via var(--tt-…) rather than {token.path}. This is intentional: it keeps the family a single point of override. Replacing var() with a token ref would inline the underlying expression into every step, duplicating CQ-units across the @supports block and breaking single-source semantics.
Token pathPatternReason
core.spacing.engine.unitFluid primitive (clamp() + cqi)Single fluid base unit driving the spacing scale; viewport fallback emitted by toCssVars
core.spacing.{1..16}Cascade-preserving alias (calc(N * var(--tt-core-spacing-engine-unit)))Each step multiplies the engine unit; using var() keeps engine.unit as the single override surface
core.font.scale.*Fluid primitiveFluid type scale; viewport fallback emitted by toCssVars
core.sizing.ramp.{ui,layout}.*Fluid primitiveFluid sizing ramps; viewport fallback emitted by toCssVars
core.sizing.hit.coarseMedia-query override targetScalar leaf. Surfaced via @media (any-pointer: coarse) against semantic.sizing.hit; useResolvedTokens substitutes it when isCoarsePointer is true

Approval criteria mirror §8: technical necessity, JSDoc on the token, registration in this table. Non-CSS consumers (useResolvedTokens) receive the unresolved expression for these paths — that is the trade-off in exchange for a CSS-native cascade. When a non-CSS consumer of one of these tokens emerges, evaluate a per-token fallback strategy at that point rather than preemptively.

9. ThemeTokens naming conventions are intentional

ThemeTokens uses colors, radii, and breakpoints (plural) alongside singular family names. These three are genuinely collection-typed families — each names a set of discrete, enumerable members rather than a unitary concept. The naming is an explicit convention, not an inconsistency. No migration to singular is planned.

The same applies to the one registered family-name asymmetry: typography is stored as core.font but exposed semantically as semantic.text. The names differ because the layers hold different things — font primitives (families, weights, scales) versus composed text meaning (display, headline, body, …) — and the asymmetry is the convention, not a defect.

10. Tokens define meaning, not implementation

Tokens do not define:

  • component APIs
  • layout composition
  • chart types
  • rendering logic
  • application behavior

Those concerns belong to components, patterns, and implementation.

11. Source-of-truth hierarchy

When the three artefacts disagree, resolve in this order:

  1. FSL Lexicon / Structural Language — authority over vocabulary and identity (what each term means; which Entity Kinds are disjoint).
  2. Types.ts — authority over contract enforcement (which tokens exist and with what shape).
  3. Family docs (this folder) — authority over consumer guidance (how to pick a token in a real case).

Identity wins over enforcement wins over guidance when the conflict is about meaning. Enforcement wins over guidance whenever the conflict is about what exists. A divergence is a defect in the lower-priority artefact unless the higher-priority artefact is itself wrong by its own rules — in which case fix it there first.


Themes and Modes

Themes and modes preserve semantic meaning, but they do not vary in the same way.

Themes

A theme may change:

  • core values
  • semantic mappings

A theme must not create a parallel semantic vocabulary for the same system intent.

Modes

Modes are controlled variations of a theme, such as light and dark.

Core tokens are immutable across modes. Modes operate at the semantic mapping layer:

  • core token values do not change
  • semantic token names do not change
  • semantic token references may point to different core tokens

When a semantic contract fails in a mode, remap — the doctrine lives in Modes.

Foundation and Extension

The system has a global foundation and a controlled extension model.

Foundation

The foundation contains the general UI token families of the system, expressed as core.{family} → semantic.{family} pairs. There is no physical foundation key in ThemeTokenscore.foundation is conceptual shorthand for the full set of non-extension families.

Extension

A new domain may extend the model when the foundation does not already solve the problem.

Example:

core.dataviz → semantic.dataviz

An extension is valid only when all of the following are true:

  1. the problem is not already solved by the foundation
  2. the concept is stable and reusable
  3. it can be expressed without component or implementation coupling
  4. it does not duplicate existing meaning

If the need is local, solve it at the pattern or application layer instead.

References and Composition

Semantic tokens typically reference core tokens.

This is what keeps names stable while allowing themes and modes to evolve.

Some families may also use composite tokens where the value is naturally applied as a bundle, such as typography styles or shadow recipes.

This is valid as long as the contract remains intact:

  • core composites remain value-only
  • semantic composites remain meaning-first
  • UI code still consumes semantic tokens only

Notation

core.* and semantic.* describe architectural layers, not required naming prefixes.

  • semantic tokens are exposed directly as the public API
  • core tokens may be namespaced or structured to prevent misuse
  • storage and build organization may vary

The architecture is fixed. The exact source notation is an implementation choice.

Enforcement

The model must be enforceable.

Validation and build must preserve at least these guarantees:

  • unique token names
  • valid and resolvable references
  • no circular references
  • semantic tokens remain the public API
  • core tokens are not consumed directly by UI code
  • no parallel vocabularies are introduced
  • semantic meaning does not change silently
  • generated outputs preserve the contract

Family-specific validation may add stricter rules where needed.

Change Rules

Changes must preserve the model. The change policy — when tokens may be added, remapped, deprecated, or removed, and how versioning applies — is defined in Governance.


Summary

The system stays scalable by protecting a small set of truths:

  • core stores values
  • semantic stores meaning
  • semantic tokens are the public API for meaning-bearing families
  • components and patterns consume semantic tokens only (infrastructure-only families are consumed directly)
  • themes may change core values; modes remap semantic references within a theme
  • families may differ in grammar; infrastructure-only families may skip the semantic layer entirely
  • extensions are controlled
  • validation and build preserve the contract

This is what makes the token system themeable, governable, and safe to evolve.