Deep Dive: Build Outputs
Why Build Outputs Matter
Design tokens are only valuable when they're consumable. Raw JSON files are the source of truth, but applications need CSS custom properties, SCSS variables, and TypeScript types. The build pipeline transforms authored tokens into these artifacts, ensuring a single source of truth propagates consistently across all consumers.
Without a build pipeline, teams end up manually copying values, creating drift between design tools and code. With it, a change to a token automatically propagates to CSS, SCSS, TypeScript, and any other format the system needs.
The Build Pipeline
The pipeline transforms modular token files through several stages, each producing artifacts for different consumers:
Source Files Build Stages Outputs
───────────────── ───────────── ─────────
core/*.tokens.json ──┐
├──▶ Compose ──▶ designTokens.json
semantic/*.tokens.json ──┘ │
├──────▶ designTokens.scss (CSS vars)
│
├──────▶ *.tokens.generated.scss (per-component)
│
└──────▶ types/designTokens.ts (TypeScript)Output Artifacts
1. Composed JSON
The composed JSON aggregates all modular token files into a single artifact. This is the canonical representation used by tooling, documentation, and runtime resolution.
| Property | Value |
|---|---|
| Output Path | ui/designTokens/designTokens.json |
| Generator | utils/designTokens/generators/compose.ts |
| Command | npm run tokens:compose |
// designTokens.json structure
{
"core": {
"color": {
"palette": {
"neutral": {
"600": {
"$type": "color",
"$value": "#555555"
}
}
}
},
"spacing": { ... },
"typography": { ... }
},
"semantic": {
"color": {
"foreground": {
"primary": {
"$type": "color",
"$value": "{core.color.mode.dark}",
"$extensions": { ... }
}
}
}
}
}Use cases: Runtime token resolution, documentation generation, design tool sync, token visualization.
2. Global CSS Custom Properties
CSS custom properties enable runtime theming and are the primary consumption method for web applications. Every token becomes a CSS variable with a flattened, kebab-case name.
| Property | Value |
|---|---|
| Output Path | app/designTokens.scss |
| Generator | utils/designTokens/generators/global.ts |
| Command | npm run tokens:globals |
/* Generated CSS custom properties */
:root {
/* Core tokens */
--core-color-palette-neutral-600: #555555;
--core-color-mode-dark: #141414;
--core-color-mode-light: #fafafa;
--core-spacing-size-04: 16px;
--core-typography-ramp-4: 16px;
/* Semantic tokens (light mode) */
--semantic-color-foreground-primary: var(--core-color-mode-dark);
--semantic-color-background-primary: var(--core-color-mode-light);
}
/* Dark mode overrides */
[data-theme="dark"] {
--semantic-color-foreground-primary: var(--core-color-mode-light);
--semantic-color-background-primary: var(--core-color-mode-dark);
}Naming Convention
Token paths are converted to CSS variable names by:
- Replacing dots with hyphens
- Converting to lowercase
- Prefixing with
--
Token Path CSS Variable
────────── ────────────
core.color.palette.neutral.600 → --core-color-palette-neutral-600
semantic.color.foreground.primary → --semantic-color-foreground-primary
semantic.components.button.primary.background
→ --semantic-components-button-primary-background3. Component-Scoped SCSS
Per-component SCSS files provide scoped token access for component styling. These files are generated alongside each component and import only the tokens that component needs.
| Property | Value |
|---|---|
| Output Pattern | ui/components/**/**.tokens.generated.scss |
| Generator | utils/designTokens/generators/generateCSSTokens.mjs |
| Command | npm run tokens:scss |
// ui/components/Button/Button.tokens.generated.scss
// Auto-generated - do not edit manually
$button-primary-background: var(--semantic-components-button-primary-background);
$button-primary-foreground: var(--semantic-components-button-primary-foreground);
$button-primary-border: var(--semantic-components-button-primary-border);
$button-secondary-background: var(--semantic-components-button-secondary-background);
// ...// Button.module.scss - consuming the generated tokens
@import './Button.tokens.generated.scss';
.button {
&--primary {
background: $button-primary-background;
color: $button-primary-foreground;
border: 1px solid $button-primary-border;
}
&--secondary {
background: $button-secondary-background;
// ...
}
}4. TypeScript Token Paths
TypeScript types provide compile-time autocomplete and type safety for token access. The TokenPath union type includes every valid token path in the system.
| Property | Value |
|---|---|
| Output Path | types/designTokens.ts |
| Generator | utils/designTokens/generators/generateTypes.mjs |
| Command | npm run tokens:types |
// types/designTokens.ts (generated)
/** All valid token paths in the design system */
export type TokenPath =
| 'core.color.palette.neutral.100'
| 'core.color.palette.neutral.200'
| 'core.color.palette.neutral.300'
// ... hundreds more
| 'semantic.color.foreground.primary'
| 'semantic.color.foreground.secondary'
| 'semantic.components.button.primary.background';
/** Color token paths only */
export type ColorTokenPath = Extract<TokenPath, `${string}.color.${string}`>;
/** Spacing token paths only */
export type SpacingTokenPath = Extract<TokenPath, `${string}.spacing.${string}`>;
/** Get a token value by path */
export function getTokenValue(path: TokenPath): string;
/** Get a CSS variable reference by token path */
export function getTokenVar(path: TokenPath): string;// Usage in application code
import { getTokenVar, type TokenPath } from '@/types/designTokens';
// Autocomplete shows all valid paths
const primaryColor = getTokenVar('semantic.color.foreground.primary');
// → 'var(--semantic-color-foreground-primary)'
// Type error if path doesn't exist
const invalid = getTokenVar('semantic.color.nonexistent');
// TypeScript Error: Argument of type '"semantic.color.nonexistent"'
// is not assignable to parameter of type 'TokenPath'Build Commands
The build pipeline can be run as a whole or in individual stages:
# Full pipeline (recommended for CI/CD)
npm run tokens:build
# Individual stages
npm run tokens:schema # Regenerate JSON Schema
npm run tokens:compose # Compose core + semantic → designTokens.json
npm run tokens:globals # Emit global CSS custom properties
npm run tokens:scss # Emit per-component SCSS token files
npm run tokens:types # Emit TypeScript TokenPath union
npm run tokens:validate # Run AJV + custom validationWhen to Run
| Scenario | Command |
|---|---|
| Added/modified a token | npm run tokens:build |
| Added a new component | npm run tokens:scss |
| CI/CD pipeline | npm run tokens:validate && npm run tokens:build |
| Debugging token issues | npm run tokens:validate |
Mode-Aware Output
Tokens with $extensions.design.paths generate mode-specific CSS. The build system reads the extension and outputs both light and dark mode values:
// Source token
{
"foreground": {
"primary": {
"$type": "color",
"$value": "{core.color.mode.dark}",
"$extensions": {
"design.paths.light": "{core.color.mode.dark}",
"design.paths.dark": "{core.color.mode.light}"
}
}
}
}
// Generated CSS
:root {
--semantic-color-foreground-primary: var(--core-color-mode-dark);
}
[data-theme="dark"] {
--semantic-color-foreground-primary: var(--core-color-mode-light);
}Pitfalls to Avoid
1. Editing Generated Files
Never edit generated files directly. Changes will be overwritten on the next build. Always modify the source token files.
// BAD: Editing generated file
// ui/components/Button/Button.tokens.generated.scss
$button-primary-background: #0066cc; // Manual edit - will be lost!
// GOOD: Edit the source token
// semantic/components/component.tokens.json
{
"button": {
"primary": {
"background": {
"$type": "color",
"$value": "{core.color.palette.blue.500}"
}
}
}
}2. Forgetting to Rebuild
Token changes don't appear in the app until the build runs. If you're seeing stale values, run npm run tokens:build.
3. Circular References in Output
CSS custom properties can reference other variables, but circular references cause runtime failures. The validator catches these at build time.
// BAD: Circular reference (caught by validator)
:root {
--color-a: var(--color-b);
--color-b: var(--color-a); // Circular!
}
// GOOD: Linear reference chain
:root {
--core-color-blue-500: #0066cc;
--semantic-color-brand: var(--core-color-blue-500);
--button-background: var(--semantic-color-brand);
}4. Missing Token Types
If TypeScript autocomplete isn't showing a token, the types may be stale. Run npm run tokens:types to regenerate.
Integration with Development Workflow
Watch Mode
For active development, run the build in watch mode to automatically regenerate outputs when token files change:
# Watch token files and rebuild on change
npm run tokens:watchPre-commit Hooks
The pre-commit hook validates tokens and ensures generated files are up to date:
# .husky/pre-commit
npm run tokens:validate
npm run tokens:build
git add ui/designTokens/designTokens.json
git add app/designTokens.scss
git add types/designTokens.tsSummary
- Composed JSON — Single source of truth for tooling and documentation
- CSS Custom Properties — Runtime theming with
--token-namevariables - Component SCSS — Scoped
$variableaccess per component - TypeScript Types — Compile-time autocomplete and type safety
- Never edit generated files — Always modify source tokens
- Run the build after token changes to see updates
Next Steps
With build outputs understood, explore how schema validation catches errors before they reach production, and how accessibility tokens encode inclusive defaults into the system.