Skip to content

Style Dictionary and Token Tooling

advanced20 min read

The Problem Style Dictionary Solves

You have your design tokens in a JSON file. Great. Now what? Your web team needs CSS custom properties. Your iOS team needs Swift constants. Your Android team needs XML resources. Your legacy codebase needs SCSS variables. Are you going to manually maintain four copies of the same data?

That is exactly what Style Dictionary automates. It reads your token source files (JSON, YAML, or JS), runs a series of transforms on them, and outputs platform-specific formats. One input, many outputs, zero manual sync.

Mental Model

Style Dictionary is a compiler for design decisions. Just like a C compiler takes one source file and outputs x86, ARM, and WASM binaries, Style Dictionary takes one token definition and outputs CSS, Swift, Kotlin, and SCSS. You write your tokens once in a platform-agnostic format, and the compiler handles the rest.

Setting Up Style Dictionary

Let us build a real pipeline from scratch. Start with installing Style Dictionary:

npm install style-dictionary

Create a token source file at tokens/color.json:

{
  "color": {
    "brand": {
      "primary": {
        "$type": "color",
        "$value": "#6366f1"
      },
      "secondary": {
        "$type": "color",
        "$value": "#ec4899"
      }
    },
    "neutral": {
      "50": { "$type": "color", "$value": "#f9fafb" },
      "200": { "$type": "color", "$value": "#e5e7eb" },
      "700": { "$type": "color", "$value": "#374151" },
      "900": { "$type": "color", "$value": "#111827" }
    }
  }
}

Create the configuration file style-dictionary.config.mjs:

export default {
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'css',
      buildPath: 'build/css/',
      files: [{
        destination: 'tokens.css',
        format: 'css/variables',
        options: {
          selector: ':root'
        }
      }]
    },
    scss: {
      transformGroup: 'scss',
      buildPath: 'build/scss/',
      files: [{
        destination: '_tokens.scss',
        format: 'scss/variables'
      }]
    },
    js: {
      transformGroup: 'js',
      buildPath: 'build/js/',
      files: [{
        destination: 'tokens.js',
        format: 'javascript/es6'
      }]
    },
    ts: {
      transformGroup: 'js',
      buildPath: 'build/ts/',
      files: [{
        destination: 'tokens.ts',
        format: 'typescript/es6-declarations'
      }]
    }
  }
}

Run the build:

npx style-dictionary build

This generates:

/* build/css/tokens.css */
:root {
  --color-brand-primary: #6366f1;
  --color-brand-secondary: #ec4899;
  --color-neutral-50: #f9fafb;
  --color-neutral-200: #e5e7eb;
  --color-neutral-700: #374151;
  --color-neutral-900: #111827;
}
// build/scss/_tokens.scss
$color-brand-primary: #6366f1;
$color-brand-secondary: #ec4899;
$color-neutral-50: #f9fafb;
// build/js/tokens.js
export const ColorBrandPrimary = "#6366f1";
export const ColorBrandSecondary = "#ec4899";
Quiz
You add a new token to your JSON source and run the Style Dictionary build. What happens to the existing output files?

The Transform Pipeline

Style Dictionary processes every token through a pipeline of transforms before writing output. Understanding this pipeline is key to customizing it.

Transforms in Detail

Transforms modify individual token properties. There are three types:

// Name transform: changes the token's name
// Input: color.brand.primary
// Output (CSS): color-brand-primary
// Output (JS): ColorBrandPrimary

// Value transform: changes the token's value
// Input: #6366f1
// Output (CSS): #6366f1 (unchanged)
// Output (iOS): UIColor(red: 0.39, green: 0.40, blue: 0.95, alpha: 1.0)

// Attribute transform: adds metadata to the token
// Input: { value: "#6366f1" }
// Output: { value: "#6366f1", attributes: { category: "color", type: "brand" } }

Style Dictionary ships with built-in transform groups for common platforms:

// 'css' transform group applies:
//   - attribute/cti (adds CTI attributes)
//   - name/kebab (color-brand-primary)
//   - time/seconds (200ms → 0.2s)
//   - size/rem (16px → 1rem)
//   - color/css (normalizes color formats)

// 'js' transform group applies:
//   - attribute/cti
//   - name/pascal (ColorBrandPrimary)
//   - size/rem
//   - color/hex (normalizes to hex)
Quiz
You have a spacing token with value 16px. In the CSS output, it appears as 1rem. In the JavaScript output, it appears as '16px'. Why the difference?

Custom Transforms

Built-in transforms handle most cases, but real projects need custom ones. Here is how you register a custom transform that converts hex colors to OKLCH:

import StyleDictionary from 'style-dictionary';

StyleDictionary.registerTransform({
  name: 'color/oklch',
  type: 'value',
  filter: (token) => token.$type === 'color',
  transform: (token) => {
    return hexToOklch(token.$value);
  }
});

StyleDictionary.registerTransformGroup({
  name: 'custom/css',
  transforms: [
    'attribute/cti',
    'name/kebab',
    'time/seconds',
    'size/rem',
    'color/oklch'  // our custom transform instead of color/css
  ]
});

Now you can use it in your config:

export default {
  source: ['tokens/**/*.json'],
  platforms: {
    css: {
      transformGroup: 'custom/css',
      buildPath: 'build/css/',
      files: [{
        destination: 'tokens.css',
        format: 'css/variables'
      }]
    }
  }
};

Custom Formats

You can also create entirely custom output formats. Suppose you want to generate a TypeScript object with proper types:

StyleDictionary.registerFormat({
  name: 'typescript/tokens',
  format: ({ dictionary }) => {
    const tokens = dictionary.allTokens.map(token =>
      `  '${token.name}': '${token.value}'`
    ).join(',\n');

    return `export const tokens = {\n${tokens}\n} as const;\n\nexport type Token = keyof typeof tokens;\n`;
  }
});

Output:

export const tokens = {
  'color-brand-primary': '#6366f1',
  'color-brand-secondary': '#ec4899',
  'color-neutral-50': '#f9fafb'
} as const;

export type Token = keyof typeof tokens;
Common Trap

When writing custom transforms, the filter function runs before transform. If your filter checks token.$type === 'color' but your tokens do not use the $type field (maybe they use type or infer type from the path), the filter will never match and your transform silently does nothing. Always verify your filter matches the shape of your actual token data.

The W3C DTCG Token Format

The W3C Design Tokens Community Group is standardizing how tokens are defined. Style Dictionary v4 supports this format natively. Here is what makes DTCG different from ad-hoc JSON:

{
  "Brand Blue": {
    "$type": "color",
    "$value": "#6366f1",
    "$description": "Primary brand color used across all products"
  },
  "Body Text": {
    "$type": "typography",
    "$value": {
      "fontFamily": "Inter, system-ui, sans-serif",
      "fontSize": "16px",
      "fontWeight": 400,
      "lineHeight": 1.5,
      "letterSpacing": "0"
    }
  },
  "Card Shadow": {
    "$type": "shadow",
    "$value": {
      "color": "#00000014",
      "offsetX": "0",
      "offsetY": "4px",
      "blur": "6px",
      "spread": "0"
    }
  },
  "Button Radius": {
    "$type": "dimension",
    "$value": "6px"
  },
  "Fade In": {
    "$type": "duration",
    "$value": "200ms"
  }
}

The $type field is what makes this format powerful — it tells tools exactly what kind of value this is, enabling type-safe transforms and validation. A tool can verify that a color token contains a valid color value, a dimension token contains a valid length, and so on.

Key Rules
  1. 1DTCG uses $ prefix for metadata: $type, $value, $description
  2. 2Composite types (typography, shadow) use objects as $value
  3. 3References use curly braces: { $value: '{color.brand.primary}' }
  4. 4Groups are plain keys without $ prefix — the parser distinguishes groups from tokens by checking for $value
Quiz
In the DTCG format, what is the purpose of the $type field?

The Figma-to-Code Pipeline

Most design systems start in Figma. The question is: how do you get tokens from Figma into your codebase without manual copy-paste?

The key tools in this pipeline:

Figma Tokens Studio (formerly Figma Tokens) is the most popular plugin. It stores tokens in a JSON format that is compatible with Style Dictionary. You can sync tokens to a GitHub repo, making the pipeline fully automated:

  1. Designer updates a color in Figma Tokens Studio
  2. Plugin commits the change to a tokens branch in GitHub
  3. CI runs Style Dictionary build
  4. PR is created with the updated CSS/SCSS/JS output
  5. Developer reviews and merges

Figma Variables REST API is Figma's native approach. Since 2023, Figma has first-party Variables that can be exported via their REST API. This is less feature-rich than Tokens Studio but has no plugin dependency.

Alternatives to Style Dictionary

Style Dictionary is the most established tool, but it is not the only option.

ToolFormatStrengthsTrade-offs
Style DictionaryJSON (DTCG support in v4)Massive ecosystem, extensible transforms, multi-platformConfig-heavy for simple projects, learning curve for custom transforms
Cobalt UIW3C DTCG nativeDTCG-first, modern ESM, fast builds, P3 color supportSmaller ecosystem, fewer built-in transforms
Theo (Salesforce)YAML or JSONPioneer in token tooling, simple APIMaintenance mode, limited platform support
Tokens StudioFigma plugin + JSONTight Figma integration, visual editor for tokensFigma-dependent, limited transform customization
When to choose Cobalt UI over Style Dictionary

Cobalt UI is worth considering if you are starting a new project in 2024+. It was built from the ground up for the W3C DTCG spec, supports P3 wide-gamut colors natively, and outputs modern CSS (OKLCH, color-mix). Style Dictionary v4 added DTCG support, but it is retrofitted onto the v3 architecture. If your project is already using Style Dictionary, there is no reason to migrate. If you are starting fresh and your tokens are exclusively DTCG format, Cobalt UI has a cleaner developer experience.

What developers doWhat they should do
Manually keeping CSS and SCSS token files in sync
Manual sync always drifts — someone updates CSS and forgets SCSS, and now platforms disagree on values
Use Style Dictionary to generate all outputs from a single source
Running Style Dictionary only in local dev
Local-only builds mean developers can forget to rebuild after token changes, shipping stale values
Run it in CI so every build generates fresh tokens
Using path-based type inference instead of explicit $type
Path inference is fragile — renaming a group can break transforms. Explicit types are self-documenting and reliable
Always set $type on tokens in DTCG format