Token architecture

Governance rules

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.

LayerResponsibilityExamples
PrimitivePhysical implementation value; normally internalcolor.palette.signal-orange
ScaleOrdered reusable valuespace.m, type.step-2
SemanticMeaning stable while its value can changecolor.accent, color.paper
ComponentA deliberately shared component decisioncomponent.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#

SourceOwnerPurpose
tokens/core.tokens.jsonToken systemCanonical token values, aliases, metadata, and public API
tokens/themes/light.tokens.jsonTheme collectionDefault semantic-colour values
tokens/themes/dark.tokens.jsonTheme collectionDark semantic-colour values using the same paths
style-dictionary.config.mjsBuild configurationBuilds the core source into CSS custom properties
scripts/token-data.mjsData and validationResolves 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 --> CHECK

npm 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#

ArtifactGenerated byConsumer
assets/css/generated/tokens.csstokens:buildShared CSS custom properties
assets/css/generated/themes.csstokens:themes[data-theme] semantic-colour overrides
data/brand/tokens.ymltokens:dataBrand token tables and colour swatches
tailwind.tokens.cjstokens:tailwindToken-backed Tailwind utilities
tokens/exports/diagram.jsontokens:diagramsRepository-facing diagram export
static/tokens/diagram.jsontokens:diagramsPublished /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#

CommandWhat it proves
npm run tokens:testAlias resolution, cycles, unknown references, hex and OKLCH contrast handling
npm run tokens:updateAll generated artifacts are rebuilt and then checked
npm run tokens:checkGenerated output is current; CSS does not bypass token governance
npm run lintToken checks plus content, interface, and reference-data checks
npm run lint:buildBoth sites render with the current implementation

When adding or changing a token:

  1. Change tokens/core.tokens.json; choose a DTCG type, semantic role, metadata, and public or internal visibility.
  2. Use an alias when the meaning should survive a palette or scale change. Do not promote palette primitives into component CSS.
  3. Add matching light and dark overrides only when the semantic colour must differ by mode.
  4. Run npm run tokens:update, inspect generated changes, then use the public token in CSS, Tailwind, a component, or a data-driven documentation page.
  5. Run npm run lint and npm run lint:build before 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.