Design Tokens

Create a single source of truth for design decisions that powers every platform and framework. Covers token taxonomy, naming conventions, transformation pipelines, multi-platform distribution, theming, and the patterns that bridge design and engineering.

Design tokens are a standardized, machine-readable representation of visual design language—such as colors, spacing, typography, and shadows—used to enforce consistency across frontend systems. They matter when multiple teams, frameworks, or platforms need to share and synchronize design decisions, especially in large-scale, multi-team environments where UI drift is inevitable.

Defining Design Tokens

Design tokens are structured data that encode a design system’s variables. They are typically stored as JSON, YAML, or a flat key-value format, with each token representing a visual property and its values across contexts.

A token file might look like this:

{
  "color": {
    "primary": {
      "base": "#3498db",
      "hover": "#2980b9",
      "active": "#1f618d"
    },
    "secondary": {
      "base": "#e67e22",
      "hover": "#d35400"
    }
  },
  "spacing": {
    "xs": 4,
    "sm": 8,
    "md": 16,
    "lg": 24,
    "xl": 32
  },
  "typography": {
    "fontFamily": "Inter, sans-serif",
    "fontSize": {
      "xs": 12,
      "sm": 14,
      "md": 16,
      "lg": 18,
      "xl": 20
    },
    "fontWeight": {
      "regular": 400,
      "medium": 500,
      "bold": 700
    }
  },
  "border": {
    "radius": {
      "sm": 4,
      "md": 8,
      "lg": 12
    },
    "width": {
      "sm": 1,
      "md": 2,
      "lg": 3
    }
  }
}

Tokens are versioned and shared via a common source—typically a Git repository, a CDN, or a design system registry. They are consumed by frontend build tools, CSS preprocessors, and runtime frameworks.

Consuming Tokens in a Frontend Build

To use design tokens in a frontend project, a build process must resolve the token values and output them to CSS or JavaScript.

Generating CSS Custom Properties

A typical setup uses postcss with postcss-design-tokens to generate CSS :root variables from a token JSON file.

// postcss.config.js
module.exports = {
  plugins: [
    require('postcss-design-tokens')({
      tokens: 'tokens.json',
      output: 'css/tokens.css',
      format: 'css',
      transform: (token) => {
        const key = `--${token.name.replace(/\.|:/g, '-')}`;
        return { [key]: token.value };
      }
    })
  ]
};

This generates:

:root {
  --color-primary-base: #3498db;
  --color-primary-hover: #2980b9;
  --color-secondary-base: #e67e22;
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --typography-fontFamily: Inter, sans-serif;
  --typography-fontSize-md: 16px;
}

Using Tokens in SASS

In a SASS project, tokens are parsed and used via @use statements.

// _tokens.scss
@use 'tokens.json' as tokens;

// Now access tokens directly
.button {
  background-color: tokens.$color-primary-base;
  padding: tokens.$spacing-md tokens.$spacing-lg;
  font-size: tokens.$typography-fontSize-md;
  border-radius: tokens.$border-radius-md;
  font-weight: tokens.$typography-fontWeight-bold;
}

The tokens.json file is imported into SASS as a map, enabling full type inference and compile-time validation.

Runtime Token Resolution

In a framework like React, tokens may be loaded at runtime to support dynamic theming.

Loading Tokens from a CDN

A React app fetches the token file on startup and applies it via a useEffect hook.

// useTokens.js
import { useEffect, useState } from 'react';

export function useTokens(url) {
  const [tokens, setTokens] = useState({});

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setTokens(data);
        // Apply to document root
        Object.entries(data).forEach(([group, values]) => {
          Object.entries(values).forEach(([key, value]) => {
            const cssVar = `--${group}-${key}`;
            document.documentElement.style.setProperty(cssVar, value);
          });
        });
      })
      .catch(err => {
        console.error('Failed to load design tokens:', err);
        // Fall back to defaults
        setTokens(defaultTokens);
      });
  }, [url]);

  return tokens;
}

// App.js
function App() {
  const tokens = useTokens('/tokens/light.json');

  return (
    <div className="app">
      <h1>App with {tokens.color?.primary?.base}</h1>
      <button className="button">Click me</button>
    </div>
  );
}

Dynamic Theme Switching

Switching themes involves loading a new token file and updating the DOM.

function ThemeSwitcher() {
  const [theme, setTheme] = useState('light');

  const switchTheme = (newTheme) => {
    fetch(`/tokens/${newTheme}.json`)
      .then(res => res.json())
      .then(tokens => {
        // Update CSS variables
        Object.entries(tokens).forEach(([group, values]) => {
          Object.entries(values).forEach(([key, value]) => {
            const cssVar = `--${group}-${key}`;
            document.documentElement.style.setProperty(cssVar, value);
          });
        });

        // Update theme class
        document.body.className = `theme-${newTheme}`;
      })
      .catch(err => {
        console.error('Theme switch failed:', err);
        // Apply fallback theme
        document.documentElement.style.setProperty('--color-primary-base', '#3498db');
      });

    setTheme(newTheme);
  }

  return (
    <div>
      <button onClick={() => switchTheme('light')}>Light</button>
      <button onClick={() => switchTheme('dark')}>Dark</button>
    </div>
  );
}

Token Variants and Contexts

Tokens can vary by context—device, theme, or interaction state. A single token may have multiple values, each tied to a condition.

A token file may include variant keys:

{
  "color": {
    "primary": {
      "base": "#3498db",
      "hover": "#2980b9",
      "active": "#1f618d",
      "focus": "#2980b9",
      "disabled": "#95a5a6"
    }
  },
  "spacing": {
    "md": {
      "base": 16,
      "tablet": 24,
      "mobile": 12
    }
  }
}

To resolve a token at runtime, a system evaluates conditions such as prefers-color-scheme, width, or data-theme.

Resolving Tokens with CSS Media Queries

@media (prefers-color-scheme: dark) {
  :root {
    --color-primary-base: #5dade2;
    --color-background-base: #1a1a1a;
  }
}

@media (max-width: 768px) {
  :root {
    --spacing-md: 12px;
    --typography-fontSize-lg: 16px;
  }
}

Resolving Tokens with JavaScript

function resolveToken(token, context) {
  const { theme, device, state } = context;

  // Prioritize: base → theme → device → state
  const path = [token, theme, device, state];
  let value = token;

  for (const key of path) {
    if (key && typeof key === 'object') {
      value = key.value || value;
    }
  }

  return value;
}

This approach enables dynamic, context-aware design systems where a single token definition supports multiple states.

Common Failure Modes

1. Token File Not Found or Invalid

A missing or malformed tokens.json file causes visual drift.

// Error in build log
Error: Failed to load design tokens: 404 Not Found
Response: {
  "error": "Token file not found",
  "requested": "/tokens/light.json"
}

2. CSS Variable Not Applied

After loading tokens, --color-primary-base is not used in any element. The issue is that :root variables must be declared in a stylesheet that loads before the component styles.

/* CSS that doesn't apply tokens */
.button {
  background-color: var(--color-primary-base); /* fails: undefined */
}

Fix: Ensure the token CSS is imported before component CSS.

3. Token Resolution Ambiguity

When multiple contexts apply, the system must resolve which value takes precedence.

A token spacing.md is defined at the root level and in a mobile context. On a tablet, the mobile value is used, but the desktop value is ignored.

{
  "spacing": {
    "md": 16,
    "mobile": {
      "md": 12
    },
    "tablet": {
      "md": 20
    }
  }
}

The system must collect all relevant contexts and resolve them in order. A common mistake is applying only the top-level value and ignoring nested contexts.

4. Token File Version Mismatch

The frontend app uses tokens version 1.2, but the design system has introduced a new color.accent token. The app fails to render accent colors.

// tokens.json (v1.2)
{
  "color": {
    "primary": "#3498db"
  }
}

The new accent token is missing in the build. To detect this, the build process should validate the token schema.

5. Token Name Conflicts

Two teams define spacing.md—one as a number, the other as a string.

{
  "spacing": {
    "md": 16,
    "md": "medium"  // conflicting
  }
}

The later definition overwrites the earlier. To prevent this, the token system should enforce unique keys or use a hierarchical naming.

Best Practices

// tokens.schema.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "color": { "type": "object" },
    "spacing": { "type": "object" },
    "typography": { "type": "object" },
    "border": { "type": "object" }
  },
  "required": ["color", "spacing"]
}

Use jsonschema to validate:

jsonschema -i tokens.json -s tokens.schema.json

Tooling

Summary

Design tokens are not just a data format—they are a workflow. They connect design to code, enabling teams to define, version, and consume visual language across platforms. The key to success is consistent naming, proper build integration, and robust runtime resolution. When tokens fail, the cause is often not the token itself, but the pipeline around it. The most subtle failures—missing CSS variables, version mismatches, and context resolution—arise not from missing features, but from unspoken assumptions about how tokens are consumed.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.