Token architecture
This is the implementation reference for the token system. Foundations > Tokens > Introduction explains the vocabulary and intent; this page records the source, transformations, outputs, consumers, and checks that make the system reliable.
Responsibility boundary#
tokens/core.tokens.json is the single source of truth for shared design decisions. It holds DTCG $value and $type fields, then adds the site’s governance metadata: category, kind, visibility, order, bilingual descriptions, and optional web or brand context.
Components and layouts consume semantic tokens. They do not own colour values, spacing scales, typography, radii, shadows, motion, or breakpoints. A repeated CSS value is a signal to add or reuse a semantic token, not to introduce another local value.
| Layer | Responsibility | Examples |
|---|---|---|
| Primitive | Physical implementation value; normally internal | color.palette.signal-orange |
| Scale | Ordered reusable value | space.m, type.step-2 |
| Semantic | Meaning stable while its value can change | color.accent, color.paper |
| Component | A deliberately shared component decision | component.theme-toggle.size |
kind classifies intent; it is not a value-resolution mechanism. A DTCG reference such as {color.palette.signal-orange} is an alias. The token data tooling resolves aliases, detects unknown references and cycles, and exposes resolved_value to documentation and contrast checks.
Sources and collections#
| Source | Owner | Purpose |
|---|---|---|
tokens/core.tokens.json | Token system | Canonical token values, aliases, metadata, and public API |
tokens/themes/light.tokens.json | Theme collection | Default semantic-colour values |
tokens/themes/dark.tokens.json | Theme collection | Dark semantic-colour values using the same paths |
style-dictionary.config.mjs | Build configuration | Builds the core source into CSS custom properties |
scripts/token-data.mjs | Data and validation | Resolves aliases, visibility, and WCAG contrast data |
Style Dictionary deliberately reads only core.tokens.json. Theme collections are selector-scoped overrides, so processing them as additional Style Dictionary sources would create duplicate-name collisions. scripts/generate-theme-tokens.mjs owns their smaller, explicit transformation instead.
outputReferences: true preserves a semantic alias in generated CSS where a CSS custom property can safely reference another generated property. It does not create aliases by itself: a source $value must contain a DTCG reference for that relationship to exist.
Build graph#
flowchart TD
CORE["tokens/core.tokens.json"]
LIGHT["tokens/themes/light.tokens.json"]
DARK["tokens/themes/dark.tokens.json"]
SD["Style Dictionary\ntokens:build"]
DATA["generate-token-data\ntokens:data"]
TW["generate-tailwind-tokens\ntokens:tailwind"]
THEME["generate-theme-tokens\ntokens:themes"]
DIAGRAM["generate-diagram-tokens\ntokens:diagrams"]
CSS["assets/css/generated/tokens.css"]
YML["data/brand/tokens.yml"]
TWC["tailwind.tokens.cjs"]
THEME_CSS["assets/css/generated/themes.css"]
JSON["static/tokens/diagram.json"]
HUGO["Hugo + PostCSS\nsite bundle"]
DOCS["Token reference tables"]
EXTERNAL["Mermaid and diagram tools"]
CHECK["tokens:check + CI"]
CORE --> SD --> CSS --> HUGO
CORE --> DATA --> YML --> DOCS
CORE --> TW --> TWC --> HUGO
CORE --> DIAGRAM --> JSON --> EXTERNAL
LIGHT --> THEME --> THEME_CSS --> HUGO
DARK --> THEME
CORE --> CHECK
LIGHT --> CHECK
DARK --> CHECK
CSS --> CHECK
YML --> CHECK
TWC --> CHECK
THEME_CSS --> CHECK
JSON --> CHECKnpm run tokens:update runs every generator, then verifies the result. Generated output is committed with its source so review and deployment see the same state.
Generated artifacts and consumers#
| Artifact | Generated by | Consumer |
|---|---|---|
assets/css/generated/tokens.css | tokens:build | Shared CSS custom properties |
assets/css/generated/themes.css | tokens:themes | [data-theme] semantic-colour overrides |
data/brand/tokens.yml | tokens:data | Brand token tables and colour swatches |
tailwind.tokens.cjs | tokens:tailwind | Token-backed Tailwind utilities |
tokens/exports/diagram.json | tokens:diagrams | Repository-facing diagram export |
static/tokens/diagram.json | tokens:diagrams | Published /tokens/diagram.json release artifact |
Hugo loads the generated core and theme CSS before the processed site stylesheet. Tailwind reads the generated bridge for public colours, type, spacing, radius, shadows, motion, and breakpoints. CSS cannot use custom properties in media-query conditions, so generated breakpoint values are intentionally literal in the Tailwind configuration.
The documentation tables render from generated YAML; their prose never duplicates the token inventory. Diagram guidance uses the resolved light values exported to JSON for Mermaid, OmniGraffle, draw.io, and XMind.
Themes and runtime selection#
Light and dark collections override the same semantic colour paths: ink, paper, line, accent, and the neutral scale. Geometry and behaviour remain in core tokens unless a mode genuinely changes them.
The base layout sets data-theme before loading the stylesheet. It uses the saved bhdicaire-theme value when a reader chose a mode; otherwise it follows prefers-color-scheme. The Theme toggle is progressive enhancement that changes the attribute and stores an explicit choice. Components only read semantic variables; none selects a theme itself.
Validation and change routine#
| Command | What it proves |
|---|---|
npm run tokens:test | Alias resolution, cycles, unknown references, hex and OKLCH contrast handling |
npm run tokens:update | All generated artifacts are rebuilt and then checked |
npm run tokens:check | Generated output is current; CSS does not bypass token governance |
npm run lint | Token checks plus content, interface, and reference-data checks |
npm run lint:build | Both sites render with the current implementation |
When adding or changing a token:
- Change
tokens/core.tokens.json; choose a DTCG type, semantic role, metadata, andpublicorinternalvisibility. - Use an alias when the meaning should survive a palette or scale change. Do not promote palette primitives into component CSS.
- Add matching light and dark overrides only when the semantic colour must differ by mode.
- Run
npm run tokens:update, inspect generated changes, then use the public token in CSS, Tailwind, a component, or a data-driven documentation page. - Run
npm run lintandnpm run lint:buildbefore committing.
Do not hard-code a repeated display value, create a component-specific theme branch, use an internal palette token directly outside the token implementation, or hand-maintain values that the generated diagram export can provide.