Design Tokens Deep Dive

Design Tokens Deep Dive

ShockStack’s design token pipeline turns a handful of JSON files into CSS variables, Tailwind theme values, TypeScript constants, and flat JSON — all from one source of truth. Here’s exactly how it works.

The Source: Token JSON Files

Everything starts in packages/tokens/tokens/. There are shared and theme-specific files:

Here’s what a color token looks like in dracula.json:

{
  "color": {
    "accent": {
      "purple": { "value": "#bd93f9", "type": "color" },
      "pink": { "value": "#ff79c6", "type": "color" },
      "green": { "value": "#50fa7b", "type": "color" }
    }
  }
}

Each token is a { value, type } pair. Style Dictionary uses the nested object structure to generate the variable name: color.accent.purple becomes --ss-color-accent-purple.

The Build: Style Dictionary

The build script (packages/tokens/build.ts) creates a theme registry and builds all theme outputs from that single list:

const themes = [
  { name: "dark", source: "dracula.json", selector: ':root, [data-theme="dark"]' },
  { name: "light", source: "light.json", selector: '[data-theme="light"]' },
  { name: "nord", source: "nord.json", selector: '[data-theme="nord"]' },
  { name: "gruvbox", source: "gruvbox.json", selector: '[data-theme="gruvbox"]' },
  { name: "midnight", source: "midnight.json", selector: '[data-theme="midnight"]' },
  { name: "dawn", source: "dawn.json", selector: '[data-theme="dawn"]' },
];

const themedDictionaries = themes.map(
  (theme) =>
    new StyleDictionary({
      source: ["tokens/base.json", `tokens/${theme.source}`, "tokens/custom.json"],
      platforms: {
        css: {
          transformGroup: "css",
          prefix: "ss",
          files: [{ destination: `${theme.name}.css`, format: "css/variables", options: { selector: theme.selector } }],
        },
        json: {
          transformGroup: "css",
          files: [{ destination: `tokens-${theme.name}.json`, format: "json/flat" }],
        },
      },
    }),
);

Theme builds and JS/TS exports run in parallel:

await Promise.all([
  ...themedDictionaries.map((dictionary) => dictionary.buildAllPlatforms()),
  base.buildAllPlatforms(),
]);

The Output: Multiple Formats

After the build, packages/tokens/dist/ contains:

FileFormatPurpose
dark.css, light.css, nord.css, gruvbox.css, midnight.css, dawn.cssCSS custom propertiesTheme-scoped variables
tokens.cssCombined CSSAll built-in themes in one import
tokens.jsES moduleProgrammatic access to values
tokens.d.tsTypeScript declarationsType-safe token access
tokens.jsonFlat JSONAll themes as JSON objects
tailwind.tokens.jsJS objectTailwind theme extension

The generated CSS looks like this:

:root, [data-theme="dark"] {
  --ss-color-accent-purple: #bd93f9;
  --ss-color-accent-pink: #ff79c6;
  --ss-color-bg-primary: #282a36;
  --ss-font-family-sans: Inter, ui-sans-serif, system-ui, sans-serif;
  --ss-radius-lg: 0.5rem;
  /* ... */
}

The ss prefix prevents collisions with other CSS variable systems. Every token gets the same prefix regardless of output format.

Connecting to Tailwind 4

The build also generates a tailwind.tokens.js that maps token names to var() references. But ShockStack uses Tailwind 4’s @theme block to wire things up directly in CSS:

@import "../../../packages/tokens/dist/tokens.css";
@import "tailwindcss";

@theme {
  --font-sans: var(--ss-font-family-sans);
  --font-mono: var(--ss-font-family-mono);

  --color-bg-primary: var(--ss-color-bg-primary);
  --color-accent-purple: var(--ss-color-accent-purple);
  --color-accent-pink: var(--ss-color-accent-pink);
  --color-border-default: var(--ss-color-border-default);

  --radius-lg: var(--ss-radius-lg);
  --shadow-md: var(--ss-shadow-md);
}

This tells Tailwind “these are your theme values.” Now bg-bg-primary, text-accent-purple, rounded-lg, and shadow-md all resolve to the design token values. When the theme selector changes data-theme, the --ss-* variables change, and every Tailwind utility updates automatically.

The Full Journey of a Token

Let’s trace accent-purple from source to usage:

  1. Sourcedracula.json: "purple": { "value": "#bd93f9" }
  2. Build — Style Dictionary transforms it into --ss-color-accent-purple: #bd93f9
  3. Themeglobal.css maps it: --color-accent-purple: var(--ss-color-accent-purple)
  4. Tailwind — generates utility: .text-accent-purple { color: var(--color-accent-purple) }
  5. Component — used in markup: <button class="bg-accent-purple">Submit</button>
  6. Theme switch — changing data-theme updates the scoped token values (for example, Dracula to Nord), and the button updates instantly

One value, defined once, consumed everywhere. Change it in the JSON file, run pnpm tokens:build, and every component in every format updates.

Why Multi-Format Output?

Different consumers need different formats:

By generating all formats from the same source, you eliminate drift. The purple in your CSS is guaranteed to be the same purple in your TypeScript is the same purple in your Tailwind utilities.

Adding Your Own Tokens

To extend the system, edit packages/tokens/tokens/custom.json:

{
  "color": {
    "brand": {
      "primary": { "value": "#6366f1", "type": "color" }
    }
  }
}

Run pnpm tokens:build, add the mapping to your @theme block, and you’ve got bg-brand-primary working across all built-in themes. The custom file is merged last, so it can override anything from base or theme files.

That’s the whole pipeline — JSON in, everything out.