Tutorial

Design Tokens for Tailwind v4: The Complete Guide for AI-Coded Projects

N
Nicolas Maes
·March 6, 2026·9 min read

Design Tokens for Tailwind v4: The Complete Guide for AI-Coded Projects

Tailwind v4 killed the JavaScript config file. No more tailwind.config.js. No more theme objects. No more extend chains that nobody understands.

The new approach is simpler and faster: CSS @theme blocks. You define tokens as CSS custom properties in your stylesheet. Tailwind reads them and generates utility classes. That's it.

For AI-coded projects, this is actually perfect. Claude Code and Cursor read CSS natively. They understand CSS custom properties. They don't always parse JavaScript config files correctly. Switching to CSS-first tokens makes AI tools more reliable.

This guide walks you through the complete setup: the @theme syntax, OKLCH color format, building a token hierarchy, and how to reference tokens in your rules files so Claude Code and Cursor use them consistently.

@theme vs tailwind.config.js: What Changed in v4

Tailwind v3 and earlier used a JavaScript config file. You'd write:

// tailwind.config.js (v3)
export default {
  theme: {
    colors: {
      primary: '#3b82f6',
      secondary: '#8b5cf6'
    },
    spacing: {
      'sm': '0.5rem',
      'md': '1rem',
      'lg': '1.5rem'
    }
  }
}

This worked, but JavaScript config files vary in structure across projects. Some import from files. Some use conditional logic. Some have typos. AI tools struggle with this variation.

Tailwind v4 moved tokens to CSS using the @theme at-rule. You write:

@import "tailwindcss";

@theme {
  --color-primary: oklch(0.64 0.25 260);
  --color-secondary: oklch(0.60 0.22 260);
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
}

This is cleaner, more portable, and universally understood by AI tools. Tailwind v4 still supports some JavaScript config for complex scenarios, but the default is CSS.

The big shift is color format. Tailwind v4 uses OKLCH instead of hex colors. OKLCH is a perceptually uniform color space, meaning equal numerical changes produce equal visual changes. This matters when you're generating a color palette algorithmically or when you want a design system that works across light and dark modes.

Building Your Token Set (Complete Code)

Here's how to structure a complete token set for a Tailwind v4 project.

Color Tokens (OKLCH Format)

OKLCH format is oklch(lightness chroma hue).

Here's a complete color palette:

@theme {
  /* Primary brand color (blue) */
  --color-primary-50: oklch(0.95 0.05 260);
  --color-primary-100: oklch(0.90 0.10 260);
  --color-primary-200: oklch(0.80 0.15 260);
  --color-primary-300: oklch(0.70 0.18 260);
  --color-primary-400: oklch(0.62 0.22 260);
  --color-primary-500: oklch(0.55 0.25 260);
  --color-primary-600: oklch(0.48 0.24 260);
  --color-primary-700: oklch(0.40 0.20 260);
  --color-primary-800: oklch(0.32 0.16 260);
  --color-primary-900: oklch(0.25 0.10 260);

  /* Secondary brand color (purple) */
  --color-secondary-50: oklch(0.95 0.05 280);
  --color-secondary-100: oklch(0.90 0.10 280);
  --color-secondary-200: oklch(0.80 0.15 280);
  --color-secondary-300: oklch(0.70 0.18 280);
  --color-secondary-400: oklch(0.62 0.22 280);
  --color-secondary-500: oklch(0.55 0.25 280);
  --color-secondary-600: oklch(0.48 0.24 280);
  --color-secondary-700: oklch(0.40 0.20 280);
  --color-secondary-800: oklch(0.32 0.16 280);
  --color-secondary-900: oklch(0.25 0.10 280);

  /* Semantic colors */
  --color-success: oklch(0.70 0.20 130);
  --color-warning: oklch(0.75 0.25 50);
  --color-error: oklch(0.60 0.25 10);
  --color-info: oklch(0.65 0.20 240);

  /* Neutral scale (gray) */
  --color-gray-50: oklch(0.98 0.01 260);
  --color-gray-100: oklch(0.95 0.01 260);
  --color-gray-200: oklch(0.93 0.01 260);
  --color-gray-300: oklch(0.90 0.01 260);
  --color-gray-400: oklch(0.80 0.01 260);
  --color-gray-500: oklch(0.68 0.01 260);
  --color-gray-600: oklch(0.50 0.01 260);
  --color-gray-700: oklch(0.43 0.01 260);
  --color-gray-800: oklch(0.30 0.01 260);
  --color-gray-900: oklch(0.20 0.01 260);

  /* Semantic background and text colors */
  --color-background: oklch(0.98 0.01 260);
  --color-surface: oklch(1 0 0);
  --color-surface-alt: oklch(0.95 0.01 260);
  --color-text-primary: oklch(0.21 0.04 260);
  --color-text-secondary: oklch(0.43 0.01 260);
  --color-text-muted: oklch(0.68 0.01 260);
  --color-border: oklch(0.93 0.01 260);
  --color-border-light: oklch(0.96 0.01 260);
  --color-divider: oklch(0.90 0.01 260);
}

The pattern for each hue is consistent: adjust lightness from 0.95 (nearly white) down to 0.25 (dark). Adjust chroma to match saturation. This creates a coherent scale AI tools understand.

Spacing Scale

Spacing tokens define padding, margin, and gap values:

@theme {
  --spacing-0: 0;
  --spacing-px: 1px;
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  --spacing-2xl: 3rem;
  --spacing-3xl: 4rem;
  --spacing-4xl: 5rem;
  --spacing-5xl: 6rem;
}

Use semantic names. --spacing-md is clearer than --spacing-4, and AI tools reference it more reliably.

Border Radius Tokens

Border radius defines corner rounding:

@theme {
  --radius-none: 0;
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 1rem;
  --radius-xl: 1.5rem;
  --radius-2xl: 2rem;
  --radius-full: 9999px;
}

Sharp corners for enterprise tools, rounded corners for consumer apps.

Shadow Tokens

Shadows add depth and hierarchy:

@theme {
  --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
               0 1px 2px 0 rgba(0, 0, 0, 0.06);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
               0 2px 4px -1px rgba(0, 0, 0, 0.06);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
               0 4px 6px -2px rgba(0, 0, 0, 0.05);
  --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
               0 10px 10px -5px rgba(0, 0, 0, 0.04);
  --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  --shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.05);
  --shadow-none: 0 0 #0000;
}

Typography Tokens

Typography defines fonts, sizes, weights, and line heights:

@theme {
  /* Font families */
  --font-family-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --font-family-serif: "Georgia", "Times New Roman", serif;
  --font-family-mono: "Monaco", "Courier New", monospace;

  /* Font sizes */
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;
  --font-size-2xl: 1.5rem;
  --font-size-3xl: 1.875rem;
  --font-size-4xl: 2.25rem;
  --font-size-5xl: 3rem;

  /* Font weights */
  --font-weight-thin: 100;
  --font-weight-extralight: 200;
  --font-weight-light: 300;
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --font-weight-extrabold: 800;
  --font-weight-black: 900;

  /* Line heights */
  --line-height-tight: 1.2;
  --line-height-snug: 1.375;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.625;
  --line-height-loose: 2;

  /* Letter spacing */
  --letter-spacing-tighter: -0.05em;
  --letter-spacing-tight: -0.025em;
  --letter-spacing-normal: 0em;
  --letter-spacing-wide: 0.025em;
  --letter-spacing-wider: 0.05em;
  --letter-spacing-widest: 0.1em;
}

Semantic Tokens vs Reference Tokens (And Why AI Needs the Difference)

Reference tokens are low-level building blocks: --color-blue-500, --spacing-4, --radius-md. They're not tied to meaning.

Semantic tokens are high-level, meaning-driven: --color-primary, --color-text, --color-success. They express intent.

AI tools work better with semantic tokens because they connect visual properties to meaning. When Claude Code sees --color-primary, it understands "this is for the brand color, use it for buttons and links." When it sees --color-blue-500, it has no context.

In your @theme, use semantic names as your primary tokens:

@theme {
  /* Semantic tokens (what AI references) */
  --color-primary: oklch(0.55 0.25 260);
  --color-primary-dark: oklch(0.40 0.20 260);
  --color-success: oklch(0.70 0.20 130);
  --color-error: oklch(0.60 0.25 10);
  --color-text: oklch(0.21 0.04 260);
  --color-text-muted: oklch(0.68 0.01 260);
  --color-border: oklch(0.93 0.01 260);
  --color-background: oklch(0.98 0.01 260);

  /* Reference tokens (internal use) */
  --color-blue-500: oklch(0.55 0.25 260);
  --color-gray-500: oklch(0.68 0.01 260);
}

In your SKILL.md or .cursorrules, reference the semantic tokens. Claude Code will follow.

Making Your Tokens AI-Readable

AI tools find your tokens in two ways: they read your CSS file directly, or you tell them the token names in your rules file.

Referencing Tokens in SKILL.md

Create a .claude/skills/design/SKILL.md file that lists your semantic tokens:

# Design System Tokens

## Color Tokens
Use these tokens instead of hardcoded colors:
- `--color-primary` for primary brand color (blue)
- `--color-primary-dark` for darker primary shade
- `--color-secondary` for secondary brand color
- `--color-success` for success states (green)
- `--color-warning` for warning states (amber)
- `--color-error` for error states (red)
- `--color-text` for primary text color
- `--color-text-secondary` for secondary text
- `--color-text-muted` for muted/disabled text
- `--color-background` for page background
- `--color-surface` for card and surface backgrounds
- `--color-border` for dividers and borders

Usage in Tailwind utilities:
- `bg-[--color-primary]` for background color
- `text-[--color-text]` for text color
- `border-[--color-border]` for border color

## Spacing Tokens
- `--spacing-xs` (0.25rem) for micro spacing
- `--spacing-sm` (0.5rem) for tight spacing
- `--spacing-md` (1rem) for standard spacing
- `--spacing-lg` (1.5rem) for loose spacing
- `--spacing-xl` (2rem) for very loose spacing

Usage: `p-[--spacing-md] gap-[--spacing-lg]`

## Radius Tokens
- `--radius-sm` for subtle rounding (input fields, small elements)
- `--radius-md` for standard rounding (cards, buttons)
- `--radius-lg` for prominent rounding (large modals, sections)
- `--radius-full` for pills and circles

Usage: `rounded-[--radius-md]`

## Typography Tokens
- `--font-size-sm` for labels and captions
- `--font-size-base` for body text
- `--font-size-lg` for subheadings
- `--font-size-xl` for section headings
- `--font-size-2xl` for page headings
- `--font-weight-normal` for body text
- `--font-weight-semibold` for emphasis
- `--font-weight-bold` for headings

Never use hardcoded font sizes like `text-lg` without a token reference.

Claude reads this file and uses the token names when building components.

Referencing Tokens in .cursorrules

In .cursor/rules/design.mdc, reference tokens in the context of Cursor's rule format:

---
globs: ["**/*.tsx", "**/*.jsx"]
---

# Tailwind v4 Token Usage

Always use these CSS custom property tokens instead of hardcoded Tailwind utilities.

## Color tokens
- Primary actions: `bg-[--color-primary] text-white`
- Secondary actions: `bg-[--color-secondary] text-white`
- Success states: `bg-[--color-success] text-white`
- Error states: `text-[--color-error] bg-[--color-error]/10`
- Body text: `text-[--color-text]`
- Secondary text: `text-[--color-text-secondary]`
- Borders: `border border-[--color-border]`
- Background: `bg-[--color-background]`

## Spacing tokens
Use tokens for all padding and gaps:
- `p-[--spacing-sm]`, `p-[--spacing-md]`, `p-[--spacing-lg]`
- `gap-[--spacing-xs]`, `gap-[--spacing-md]`
- Never use hardcoded `p-4` or `gap-2`

## Radius tokens
- `rounded-[--radius-sm]` for inputs
- `rounded-[--radius-md]` for cards and standard elements
- `rounded-[--radius-lg]` for large elements
- `rounded-[--radius-full]` for pills

## Typography
- `text-[--font-size-base]` for body text
- `text-[--font-size-lg]` for subheadings
- `text-[--font-size-2xl]` for headings
- `font-[--font-weight-semibold]` for emphasis
- Never use hardcoded `text-lg` or `font-bold`

Complete globals.css Example

Here's the complete, production-ready globals.css file you can copy and adapt:

@import "tailwindcss";

@theme {
  /* Color Tokens - Primary Brand (Blue) */
  --color-primary-50: oklch(0.95 0.05 260);
  --color-primary-100: oklch(0.90 0.10 260);
  --color-primary-200: oklch(0.80 0.15 260);
  --color-primary-300: oklch(0.70 0.18 260);
  --color-primary-400: oklch(0.62 0.22 260);
  --color-primary-500: oklch(0.55 0.25 260);
  --color-primary-600: oklch(0.48 0.24 260);
  --color-primary-700: oklch(0.40 0.20 260);
  --color-primary-800: oklch(0.32 0.16 260);
  --color-primary-900: oklch(0.25 0.10 260);

  --color-primary: var(--color-primary-500);
  --color-primary-dark: var(--color-primary-700);
  --color-primary-light: var(--color-primary-300);

  /* Color Tokens - Secondary (Purple) */
  --color-secondary-500: oklch(0.55 0.25 280);
  --color-secondary-600: oklch(0.48 0.24 280);
  --color-secondary-700: oklch(0.40 0.20 280);

  --color-secondary: var(--color-secondary-500);
  --color-secondary-dark: var(--color-secondary-700);

  /* Semantic Colors */
  --color-success: oklch(0.70 0.20 130);
  --color-warning: oklch(0.75 0.25 50);
  --color-error: oklch(0.60 0.25 10);
  --color-info: oklch(0.65 0.20 240);

  /* Neutral Scale (Gray) */
  --color-gray-50: oklch(0.98 0.01 260);
  --color-gray-100: oklch(0.95 0.01 260);
  --color-gray-200: oklch(0.93 0.01 260);
  --color-gray-300: oklch(0.90 0.01 260);
  --color-gray-400: oklch(0.80 0.01 260);
  --color-gray-500: oklch(0.68 0.01 260);
  --color-gray-600: oklch(0.50 0.01 260);
  --color-gray-700: oklch(0.43 0.01 260);
  --color-gray-800: oklch(0.30 0.01 260);
  --color-gray-900: oklch(0.20 0.01 260);

  /* Background and Text Colors */
  --color-background: oklch(0.98 0.01 260);
  --color-surface: oklch(1 0 0);
  --color-surface-alt: oklch(0.95 0.01 260);
  --color-text: oklch(0.21 0.04 260);
  --color-text-secondary: oklch(0.43 0.01 260);
  --color-text-muted: oklch(0.68 0.01 260);
  --color-border: oklch(0.93 0.01 260);
  --color-border-light: oklch(0.96 0.01 260);

  /* Spacing Scale */
  --spacing-0: 0;
  --spacing-px: 1px;
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  --spacing-2xl: 3rem;
  --spacing-3xl: 4rem;
  --spacing-4xl: 5rem;

  /* Border Radius */
  --radius-none: 0;
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 1rem;
  --radius-xl: 1.5rem;
  --radius-2xl: 2rem;
  --radius-full: 9999px;

  /* Shadows */
  --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
  --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  --shadow-none: 0 0 #0000;

  /* Typography */
  --font-family-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --font-family-serif: "Georgia", "Times New Roman", serif;
  --font-family-mono: "Monaco", "Courier New", monospace;

  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;
  --font-size-2xl: 1.5rem;
  --font-size-3xl: 1.875rem;
  --font-size-4xl: 2.25rem;
  --font-size-5xl: 3rem;

  --font-weight-light: 300;
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --font-weight-black: 900;

  --line-height-tight: 1.2;
  --line-height-snug: 1.375;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.625;
  --line-height-loose: 2;
}

@layer base {
  body {
    @apply font-sans text-[--font-size-base] leading-[--line-height-normal] text-[--color-text] bg-[--color-background];
  }

  h1 {
    @apply text-[--font-size-4xl] font-bold leading-[--line-height-tight];
  }

  h2 {
    @apply text-[--font-size-3xl] font-bold leading-[--line-height-snug];
  }

  h3 {
    @apply text-[--font-size-2xl] font-semibold leading-[--line-height-snug];
  }

  h4 {
    @apply text-[--font-size-xl] font-semibold leading-[--line-height-normal];
  }

  p {
    @apply text-[--font-size-base] leading-[--line-height-normal] text-[--color-text];
  }

  a {
    @apply text-[--color-primary] hover:text-[--color-primary-dark] transition-colors;
  }

  button {
    @apply transition-colors duration-200;
  }

  input,
  textarea,
  select {
    @apply bg-[--color-surface] border border-[--color-border] rounded-[--radius-md] px-[--spacing-md] py-[--spacing-sm] text-[--color-text];
  }

  input:focus,
  textarea:focus,
  select:focus {
    @apply outline-none ring-2 ring-[--color-primary] ring-offset-2;
  }
}

Import this in your app's root layout and you're set. Tailwind generates utilities from every token. Claude Code and Cursor see the tokens and use them.

Frequently Asked Questions

Q: How do design tokens work in Tailwind v4?

A: Tailwind v4 replaced JavaScript config files with CSS-first @theme blocks. You define tokens as CSS custom properties in globals.css, then expose them to Tailwind with @theme inline. Tailwind generates utility classes from your tokens automatically. Writing --color-primary: oklch(0.65 0.24 260) in @theme makes bg-primary and text-primary available as utility classes throughout your project.

Q: What is OKLCH and why does Tailwind v4 use it?

A: OKLCH is a perceptually uniform color space where equal numerical changes produce equal visual changes. Unlike hex or HSL, adjusting lightness in OKLCH produces predictable results. Tailwind v4 adopted it for theming because it generates more visually consistent color scales. Format: oklch(lightness chroma hue) where lightness is 0-1, chroma is 0-0.4, and hue is 0-360 degrees.

Q: Should I use @theme or tailwind.config.js for design tokens?

A: Use @theme (CSS-first) for new projects on Tailwind v4. AI tools read CSS natively and reliably. They don't always parse JavaScript config files correctly due to varying import paths and module systems across training data. CSS custom properties are universally understood by every AI coding tool: Claude Code, Cursor, Copilot, and others.


Once you've set up your Tailwind v4 tokens, the next step is documenting them properly for AI tools. Read "SKILL.md vs CLAUDE.md vs .cursorrules" to see how to reference your tokens in rules files so Claude Code and Cursor actually use them.

And if you want to skip the manual token design entirely, generate your token set in Matchkit and download complete globals.css file ready to use.

#tailwind#design-tokens#css-variables#oklch#ai-coding
All articles

More articles

Design

MCP Servers for Design: How AI Coding Tools Are Getting a Taste Layer

April 5, 2026·7 min read
Case Study

The Design Gap Between $0/mo and $49/mo SaaS (It's 11 CSS Values)

April 2, 2026·7 min read
Case Study

Agency Workflow: From Client Brief to Branded Prototype with AI Coding

March 29, 2026·7 min read