Learning Track
Filter content by your role to see the most relevant sections
Code Tooling
Integrate tokens into code with a validated pipeline: DTCG schema checks, deterministic generation to CSS custom properties and types, layer-boundary lints, accessibility rules in the lint config, and component contracts enforced at push time.
Why This Matters
A token system in code is only as strong as its pipeline. Hand-maintained CSS variables rot on the first busy week; unvalidated token JSON rots silently until a typo's reference resolves to nothing in production. The code-side tooling exists to make correctness cheaper than drift: schemas that reject malformed tokens at build time, generation that makes the emitted CSS deterministic, and lints that catch the boundary violations review misses.
This repository's pipeline is deliberately DTCG-native and custom—the format is the standard W3C shape, but the build is purpose-built around the layering and brand machinery you have met in the foundations pages. Where an industry tool occupies the same niche (Style Dictionary for generation, Tailwind for consumption ergonomics, eslint-plugin-jsx-a11y for markup accessibility), this page names it and shows what this system does instead and why.
Core Concepts: Validate, Generate, Lint, Enforce
The Build: Compose, Then Emit
The pipeline is two commands in package.json:
tokens:build tsx utils/designTokens/runners/build.ts
&& node generateCSSTokens.mjs
tokens:build:full same, with --no-incremental (clean builds)
// build.ts composes: core + semantic + brands → designTokens.json
// generateCSSTokens.mjs emits: app/designTokens.scss
// (cascade layers, .light/.dark, [data-brand], [data-density])Incremental mode skips unchanged work; the full mode is the CI-grade path and also gates next build itself (build runs the token build first, so a token error is a build error, not a runtime surprise). The emitted stylesheet is the one you have seen throughout: 316 core custom properties, semantic aliases, theme and brand layers—the whole theming architecture as a build artifact.
Validation Before Generation
The token sources are schema-checked with a W3C DTCG validator (w3c-validator.mjs plus the repo schema) covering the format's invariants—types match values, references resolve, no cycles. Around it live the repo's own checks, runnable as a single chain:
npm run validate # the full chain
contracts:validate # component contracts well-formed
contracts:traceability # bindings resolve to real tokens
contracts:tokens # scoped CSS matches contracts
tokens:lint # kebab vars + reference directions
styles:tokens # style values match tokens
typecheck && lint && format:checkThe design principle: every claim the foundations pages make—references flow one way, names are kebab, components consume their own tier—has a command that checks it. Docs that cannot be executed are suggestions; these are gates.
The Layer Lints
Two scripts do the boundary enforcement:check-token-references.mjs walks every token reference and fails direction violations (core depending on semantic, components reaching past their scope), and check-kebab-token-vars.mjs holds the emitted naming to the deterministic transform. Together they make the atomic-vs-semantic doctrine mechanical: the layer boundary is not a diagram, it is a failing check.
Accessibility in the Lint Config
Markup-level accessibility rides the standard ESLint plugin—next/core-web-vitals registers jsx-a11y, and this repo promotes key rules to hard errors:
// eslint.config.mjs (excerpt)
'jsx-a11y/alt-text': 'error',
'jsx-a11y/anchor-has-content': 'error',
'jsx-a11y/anchor-is-valid': 'error',
// …with axe-core (wcag2a, wcag2aa, best-practice) in the
// vitest setup for rendered-output auditing.The split of labor is deliberate: jsx-a11y catches the static sins (missing alt, empty anchors) at lint speed; axe catches the rendered sins (contrast, roles, names) in the component tests; and the token validator catches the numeric sins (pair ratios) at build time. Three layers, no gaps between "looks fine" and "checked."
The Industry Tools, Honestly Placed
- Style Dictionary is the category-standard generator (JSON in, any platform out). This repo's generator is custom because the layering—brand accent remapping, density slots, the core/semantic namespace rule—is opinionated enough that a bespoke 150-line transform beats a configured framework. Teams starting fresh should evaluate Style Dictionary first and go custom only at the same provocation.
- Tailwind maps tokens to utility ergonomics. This repo keeps CSS custom properties and scoped component tokens instead—consumption stays semantic (
var(--ds-card-…)) rather than utility-composed, which preserves the layer boundary in the usage syntax itself. - eslint-plugin-jsx-a11y — used here as above, the standard choice, promoted to errors.
System Roles: Who Owns the Pipeline
Engineering Impact
Engineers own the pipeline end to end: build scripts, validators, generators, and the discipline that the generated artifacts are never hand-edited. A pipeline PR changes how every token ships and reviews accordingly.
Design Impact
Designers consume the pipeline's outputs on their side (mirrored variables, generated libraries) and author its inputs (token JSON or plugin-pushed values, per the authority decision). The validation chain is the designer's error message as much as the engineer's.
Governance Impact
Governance owns which checks block versus warn, and when the chain runs (pre-commit for speed, pre-push and CI for certainty—the automation page's territory). A check nobody can bypass is a policy; everything else is advice.
Design & Code Interplay
To a designer, the pipeline shows up as reliability: the variables in the file always match the CSS in the product, because both are projections of the same validated JSON. When the file says neutral/600 and the inspector says --core-color-palette-neutral-600, that agreement is the pipeline working—and the moment it breaks, the chain says where.
The developer loop against the pipeline, end to end:
# Author a token change (JSON), then:
npm run tokens:build # compose + emit (fast, incremental)
npm run validate # the full claim chain
git commit # lint-staged: prettier+eslint on touched files
git push # pre-push: contracts + lint + the heavy checks
# What you must never do:
# - edit app/designTokens.scss by hand (generated; next build overwrites)
# - edit ui/components/*/*.tokens.css by hand (generated from contracts)
# - merge with a failing validate chain ("just this once" is policy debt)Applied Example: A Bad Token, Caught Three Ways
Watch the tooling catch one realistic mistake—a semantic token referencing a hex instead of a core path:
- Schema/validation:
tokens:schemaand the DTCG validator reject the malformed$valuebefore anything emits—the cheapest catch, at author time. - Layer lint: had it slipped through as a reference to the wrong layer,
check-token-referencesfails the direction; the error names the token and the rule, not a stack trace three systems away. - Traceability:
contracts:traceabilitycatches the consumer side—any component binding whoseresolvesTono longer exists—so the blast radius is enumerated by the chain, not discovered in a screenshot review.
Three nets, each cheaper than the one below it. That layering of checks is the entire craft of pipeline design: catch wrong at the moment it is cheapest to be right.
Constraints & Trade-offs
- Custom pipeline vs framework: bespoke generators fit the layering exactly and cost maintenance; Style Dictionary buys community support and costs configuration ceiling. This repo chose custom at the point its opinions outgrew configuration.
- Check speed vs check coverage: the fast checks run on every commit (lint-staged), the chain on push. Moving a slow check earlier buys safety with friction—the placement is a decision, not a default.
- Errors vs warnings: every rule promoted to error removes a judgment call from review; every one left warning retains flexibility. The ledger of which rules sit where is policy worth reviewing quarterly.
Common Pitfalls & Failure Modes
1. Editing generated artifacts
/* Bad: app/designTokens.scss — AUTO-GENERATED, overwritten next build */
/* Good: Edit the JSON, run the build, review the generated diff */2. Disabling a lint to ship
eslint-disable on a token rule is policy debt with interest—the boundary erodes one exception at a time. Waivers belong in configuration with expiry, not in code comments forever.
3. Validating only the happy path
A chain that never sees malformed input stops being trusted. Feed it broken fixtures in tests (the repo's validator tests do) or it will silently rot.
4. Pipeline drift from docs
When a check changes meaning, the foundations pages that cite it change in the same commit—or the docs become a second, wronger source of truth.
Verification Checklist
Additional Resources
- Automation & CI/CD — where the chain runs (
/blueprints/foundations/tooling/automation) - Token pipeline deep-dives — build outputs, the resolver module, schema validation (
/blueprints/foundations/tokensand children) - Style Dictionary — the category-standard generator (
https://styledictionary.com) - The sources —
utils/designTokens/runners/build.ts,generators/generateCSSTokens.mjs,eslint.config.mjs,scripts/*
Related Concepts
Reflection Questions
Apply This Concept
Trace one token from JSON edit to production CSS: name every check it passes, in order, and what each is protecting against.
Reflect
Your team proposes replacing the custom generator with Style Dictionary. List what you would gain, what you must recreate, and which repo-specific invariants would be hardest to express.
