HandoffPro

Design Tokens with Tailwind CSS Configuration

·8 min read

If you're using Tailwind CSS and struggling to maintain design consistency across components—or worse, copy-pasting hex color values and pixel spacing directly into utility classes—you're missing the power of design tokens. Design tokens are the source of truth for your design system, and Tailwind's utility-first architecture makes it one of the best frameworks for token-driven development.

In this guide, you'll learn how to integrate design tokens into Tailwind CSS theme configuration, map token values to utilities, and even set up dynamic theming with CSS variables.

TLDR

Design tokens are design decisions stored as data (colors, spacing, typography) that can be transformed into platform-specific formats. Tailwind CSS has native support for design tokens through its theme configuration. You define tokens in JSON or CSS files, import them into tailwind.config.js, and map them to Tailwind's theme keys like colors, spacing, and fontSize.

Key takeaways:

  • Tailwind's theme.extend property maps design tokens to utility class values
  • Use Style Dictionary to transform tokens from JSON into Tailwind-compatible formats
  • CSS custom properties enable dynamic runtime theming without rebuilding Tailwind
  • Tailwind v4's new @theme directive simplifies token integration with CSS-first config
  • Token-driven Tailwind utilities enforce design system consistency across components

Why Tailwind for Design Tokens?

Tailwind's utility-first approach maps naturally to the concept of design tokens. Every Tailwind utility class is essentially a token: bg-blue-500 references a color token, p-4 references a spacing token, text-lg references a typography token.

The difference between hard-coded Tailwind defaults and token-driven Tailwind is where those values come from. Instead of relying on Tailwind's default palette (blue-500, gray-700), you define semantic tokens that match your design system (primary, textSecondary, spacingMd).

This gives you:

  • Cross-platform consistency: The same tokens can be used in web (Tailwind), iOS (Swift), and Android (Kotlin)
  • Single source of truth: Change a token value in one place, update everywhere
  • Semantic naming: bg-primary is more meaningful than bg-blue-500 in your codebase
  • Design system enforcement: Only approved token values are available as utilities

Tailwind CSS has excellent support for design tokens through its theme configuration system, making it one of the most token-friendly frameworks available.

Mapping Design Tokens to Tailwind Theme

Tailwind's tailwind.config.js file provides a theme object where you define or extend design values. Here's how design tokens map to Tailwind's theme structure:

Token Category Tailwind Theme Key Example Token Example Utility
Colors colors primary: "#3b82f6" bg-primary
Spacing spacing md: "16px" p-md, m-md
Typography fontSize, fontFamily bodyLarge: "18px" text-bodyLarge
Border Radius borderRadius card: "12px" rounded-card
Shadows boxShadow elevation1: "0 2px 4px rgba(0,0,0,0.1)" shadow-elevation1

You use the theme.extend property to add your custom tokens without overwriting Tailwind's defaults:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: "#3b82f6",
        secondary: "#8b5cf6",
        textPrimary: "#0f172a",
        textSecondary: "#64748b",
      },
      spacing: {
        xs: "4px",
        sm: "8px",
        md: "16px",
        lg: "24px",
        xl: "32px",
      },
      fontSize: {
        bodySmall: "14px",
        bodyMedium: "16px",
        bodyLarge: "18px",
        headingSmall: "24px",
      },
    },
  },
};

Now you can use bg-primary, p-md, and text-bodyLarge as utilities in your components, and they'll reference your token values.

Code Example: Complete Tailwind Config with Tokens

Here's a real-world example showing how to import design tokens from a JSON file and map them to Tailwind's theme configuration:

tokens.json (your design token source file):

{
  "color": {
    "brand": {
      "primary": { "value": "#3b82f6" },
      "secondary": { "value": "#8b5cf6" }
    },
    "text": {
      "primary": { "value": "#0f172a" },
      "secondary": { "value": "#64748b" },
      "disabled": { "value": "#94a3b8" }
    },
    "background": {
      "default": { "value": "#ffffff" },
      "subtle": { "value": "#f8fafc" }
    }
  },
  "spacing": {
    "xs": { "value": "4px" },
    "sm": { "value": "8px" },
    "md": { "value": "16px" },
    "lg": { "value": "24px" },
    "xl": { "value": "32px" },
    "2xl": { "value": "48px" }
  },
  "font": {
    "size": {
      "sm": { "value": "14px" },
      "base": { "value": "16px" },
      "lg": { "value": "18px" },
      "xl": { "value": "24px" }
    },
    "family": {
      "sans": { "value": "Inter, system-ui, sans-serif" }
    }
  },
  "radius": {
    "sm": { "value": "4px" },
    "md": { "value": "8px" },
    "lg": { "value": "12px" },
    "full": { "value": "9999px" }
  }
}

tailwind.config.js (importing and mapping tokens):

const tokens = require('./tokens.json');
 
// Helper function to extract token values
function extractTokenValues(tokenGroup) {
  const values = {};
  for (const [key, value] of Object.entries(tokenGroup)) {
    if (value.value) {
      values[key] = value.value;
    } else {
      // Handle nested token groups
      Object.assign(values, extractTokenValues(value));
    }
  }
  return values;
}
 
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {
      colors: {
        primary: tokens.color.brand.primary.value,
        secondary: tokens.color.brand.secondary.value,
        textPrimary: tokens.color.text.primary.value,
        textSecondary: tokens.color.text.secondary.value,
        textDisabled: tokens.color.text.disabled.value,
        bgDefault: tokens.color.background.default.value,
        bgSubtle: tokens.color.background.subtle.value,
      },
      spacing: extractTokenValues(tokens.spacing),
      fontSize: extractTokenValues(tokens.font.size),
      fontFamily: {
        sans: [tokens.font.family.sans.value],
      },
      borderRadius: extractTokenValues(tokens.radius),
    },
  },
  plugins: [],
};

This approach gives you full control over token mapping while keeping your design tokens in a separate, version-controlled file that can be consumed by other platforms.

Using CSS Variables with Tailwind

For dynamic theming (light/dark mode, user-selected themes), you can combine design tokens with CSS custom properties. This enables runtime theme changes without rebuilding Tailwind.

Step 1: Define tokens as CSS variables

/* styles/tokens.css */
:root {
  --color-primary: #3b82f6;
  --color-secondary: #8b5cf6;
  --color-text-primary: #0f172a;
  --color-text-secondary: #64748b;
  --color-bg-default: #ffffff;
  --color-bg-subtle: #f8fafc;
}
 
[data-theme="dark"] {
  --color-primary: #60a5fa;
  --color-secondary: #a78bfa;
  --color-text-primary: #f8fafc;
  --color-text-secondary: #cbd5e1;
  --color-bg-default: #0f172a;
  --color-bg-subtle: #1e293b;
}

Step 2: Reference CSS variables in Tailwind config

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: 'var(--color-primary)',
        secondary: 'var(--color-secondary)',
        textPrimary: 'var(--color-text-primary)',
        textSecondary: 'var(--color-text-secondary)',
        bgDefault: 'var(--color-bg-default)',
        bgSubtle: 'var(--color-bg-subtle)',
      },
    },
  },
};

Step 3: Toggle themes at runtime

// components/ThemeToggle.jsx
function ThemeToggle() {
  const toggleTheme = () => {
    const currentTheme = document.documentElement.getAttribute('data-theme');
    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
    document.documentElement.setAttribute('data-theme', newTheme);
  };
 
  return (
    <button onClick={toggleTheme} className="bg-primary text-white px-4 py-2 rounded-md">
      Toggle Theme
    </button>
  );
}

Now when you click the button, all components using token-driven utilities (bg-primary, text-textPrimary) will instantly update to the new theme values. You can use tokens with React components by passing them as props or consuming CSS variables directly.

Tailwind v4 Token Integration

Tailwind v4 introduces a major shift: CSS-first configuration. Instead of defining your theme in JavaScript, you define it directly in CSS using the new @theme directive.

Before (Tailwind v3):

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#3b82f6',
      },
    },
  },
};

After (Tailwind v4):

/* styles/theme.css */
@import "tailwindcss";
 
@theme {
  --color-primary: #3b82f6;
  --color-secondary: #8b5cf6;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --font-size-base: 1rem;
}

This CSS-first approach simplifies token integration because your design tokens are already in CSS format. You can combine with CSS variables for dynamic theming by defining tokens as custom properties and referencing them in @theme blocks.

Benefits for token-driven workflows:

  • No JavaScript required for theme configuration
  • Direct CSS variable integration
  • Better build performance (CSS parsing is faster than JS config evaluation)
  • Easier to understand for designers familiar with CSS

Tailwind v4 is still in beta as of early 2026, but the CSS-first approach will be the recommended pattern for token integration going forward.

Automated Token-to-Tailwind Pipeline

For large design systems, manually mapping tokens to Tailwind config becomes tedious. Tools like Style Dictionary can automate this process by transforming tokens from a source format (JSON) into platform-specific outputs (Tailwind config, CSS variables, iOS Swift, etc.).

Step 1: Define tokens in Style Dictionary format

{
  "color": {
    "brand": {
      "primary": { "value": "#3b82f6" }
    }
  }
}

Step 2: Create a custom Style Dictionary format for Tailwind

// build-tokens.js
const StyleDictionary = require('style-dictionary');
 
StyleDictionary.registerFormat({
  name: 'tailwind/theme',
  formatter: ({ dictionary }) => {
    const colors = {};
    dictionary.allTokens.forEach(token => {
      if (token.type === 'color') {
        colors[token.name] = token.value;
      }
    });
    return `module.exports = ${JSON.stringify({ theme: { extend: { colors } } }, null, 2)};`;
  },
});
 
StyleDictionary.buildPlatform({
  source: ['tokens/**/*.json'],
  platforms: {
    tailwind: {
      transformGroup: 'css',
      buildPath: 'src/',
      files: [{
        destination: 'tailwind.theme.js',
        format: 'tailwind/theme',
      }],
    },
  },
});

Step 3: Import generated config into Tailwind

// tailwind.config.js
const tokens = require('./src/tailwind.theme.js');
 
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  ...tokens,
};

Now whenever you update your design tokens, running node build-tokens.js regenerates the Tailwind theme config automatically. Style Dictionary is the industry-standard tool for this kind of multi-platform token transformation.

FAQ

Q: Can I use design tokens with Tailwind v4?

A: Yes. Tailwind v4 introduces a new CSS-first configuration using the @theme directive in CSS files instead of JavaScript config files. You can define design tokens as CSS custom properties and reference them directly in @theme blocks for a more streamlined token integration workflow.

Q: How do I use CSS variables with Tailwind for dynamic theming?

A: Define your tokens as CSS custom properties in :root (e.g., --color-primary: #3b82f6), reference them in tailwind.config.js using var() syntax (colors: { primary: 'var(--color-primary)' }), and switch themes by updating CSS variables via JavaScript. This enables runtime theme changes without rebuilding Tailwind.

Q: Should I put design tokens directly in Tailwind config or keep them separate?

A: Keep tokens in a separate JSON or CSS file and import them into Tailwind config. This separation enables cross-platform token usage (iOS, Android, web), makes tokens source-controllable independently of framework config, and allows tools like Style Dictionary to transform tokens for multiple platforms from a single source.

Stop Extracting Design Values Manually

Upload a Figma screenshot and get JSONC tokens + a Claude-ready prompt in 30 seconds.

Cookie Preferences

We use cookies for analytics and advertising. Essential cookies are always enabled. Learn more

Design Tokens with Tailwind CSS Configuration | HandoffPro