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.

PropertyValue
Output Pathui/designTokens/designTokens.json
Generatorutils/designTokens/generators/compose.ts
Commandnpm 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.

PropertyValue
Output Pathapp/designTokens.scss
Generatorutils/designTokens/generators/global.ts
Commandnpm 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:

  1. Replacing dots with hyphens
  2. Converting to lowercase
  3. 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-background

3. 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.

PropertyValue
Output Patternui/components/**/**.tokens.generated.scss
Generatorutils/designTokens/generators/generateCSSTokens.mjs
Commandnpm 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.

PropertyValue
Output Pathtypes/designTokens.ts
Generatorutils/designTokens/generators/generateTypes.mjs
Commandnpm 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 validation

When to Run

ScenarioCommand
Added/modified a tokennpm run tokens:build
Added a new componentnpm run tokens:scss
CI/CD pipelinenpm run tokens:validate && npm run tokens:build
Debugging token issuesnpm 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:watch

Pre-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.ts

Summary

  • Composed JSON — Single source of truth for tooling and documentation
  • CSS Custom Properties — Runtime theming with --token-name variables
  • Component SCSS — Scoped $variable access 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.

← Back to Design Tokens