| name | design-tokens |
| description | Apply design token patterns using Tailwind CSS 4 @theme directive: CSS variables, semantic naming, color systems, typography scales, spacing, dark mode. Use when designing UI systems, reviewing design consistency, or establishing brand guidelines. Integrates with frontend-design skill for aesthetic execution. |
Design Tokens
Modern design token patterns using Tailwind CSS 4's @theme directive for consistent, maintainable UI design systems.
Philosophy
Design tokens are the single source of truth for design decisions. Rather than scattering magic numbers and colors throughout your codebase, centralize them as semantic variables that can be referenced everywhere.
CSS-first in Tailwind 4: No more JavaScript configuration. Define tokens directly in CSS using the @theme directive, making them available as Tailwind utilities and CSS variables.
Semantic naming over descriptive: --color-primary beats --color-blue-500. Semantic tokens communicate intent; descriptive tokens communicate implementation.
Why Tailwind CSS 4 @theme
Tailwind CSS 4 revolutionizes design token management:
- CSS-native: Define tokens in CSS, not JavaScript config
- Type-safe: Auto-completion in editors
- No build step overhead: Faster development
- CSS variable integration: Use tokens anywhere (
var(--color-primary)) - Dark mode built-in: Theme-aware color switching
Migration from Tailwind 3: Delete tailwind.config.js, move to CSS @theme directive.
Installation & Setup
# Install Tailwind CSS 4 (beta as of 2025)
pnpm add tailwindcss@next @tailwindcss/vite@next
# For Next.js projects
pnpm add tailwindcss@next @tailwindcss/postcss@next
Basic @theme Structure
/* app/globals.css or src/styles/theme.css */
@import "tailwindcss";
@theme {
/* Color System */
--color-primary: oklch(0.6 0.2 250);
--color-secondary: oklch(0.5 0.15 180);
--color-accent: oklch(0.7 0.25 30);
--color-success: oklch(0.6 0.18 140);
--color-warning: oklch(0.7 0.2 80);
--color-error: oklch(0.6 0.22 20);
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.2 0 0);
--color-muted: oklch(0.95 0 0);
--color-border: oklch(0.9 0 0);
/* Typography Scale */
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-size-4xl: 2.25rem; /* 36px */
--font-size-5xl: 3rem; /* 48px */
--font-size-6xl: 3.75rem; /* 60px */
/* Font Families */
--font-sans: "Inter Variable", system-ui, sans-serif;
--font-serif: "Merriweather", Georgia, serif;
--font-mono: "JetBrains Mono", "Fira Code", monospace;
--font-display: "Clash Display", "Inter Variable", sans-serif;
/* Spacing Scale */
--spacing-xs: 0.25rem; /* 4px */
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 1rem; /* 16px */
--spacing-lg: 1.5rem; /* 24px */
--spacing-xl: 2rem; /* 32px */
--spacing-2xl: 3rem; /* 48px */
--spacing-3xl: 4rem; /* 64px */
/* Border Radius */
--radius-sm: 0.25rem; /* 4px */
--radius-md: 0.5rem; /* 8px */
--radius-lg: 0.75rem; /* 12px */
--radius-xl: 1rem; /* 16px */
--radius-2xl: 1.5rem; /* 24px */
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* Transitions */
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
/* Z-Index Layers */
--z-base: 0;
--z-dropdown: 1000;
--z-sticky: 1100;
--z-overlay: 1200;
--z-modal: 1300;
--z-popover: 1400;
--z-tooltip: 1500;
}
Dark Mode
Define dark mode variants using @media (prefers-color-scheme: dark):
@theme {
/* Light mode (default) */
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.2 0 0);
--color-muted: oklch(0.95 0 0);
--color-border: oklch(0.9 0 0);
@media (prefers-color-scheme: dark) {
/* Dark mode overrides */
--color-background: oklch(0.15 0 0);
--color-foreground: oklch(0.95 0 0);
--color-muted: oklch(0.2 0 0);
--color-border: oklch(0.3 0 0);
}
}
Manual dark mode toggle (class-based):
@theme {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.2 0 0);
}
.dark {
--color-background: oklch(0.15 0 0);
--color-foreground: oklch(0.95 0 0);
}
// Dark mode toggle component
'use client'
import { useEffect, useState } from 'react'
export function DarkModeToggle() {
const [isDark, setIsDark] = useState(false)
useEffect(() => {
const isDark = localStorage.getItem('theme') === 'dark'
setIsDark(isDark)
document.documentElement.classList.toggle('dark', isDark)
}, [])
const toggle = () => {
const newIsDark = !isDark
setIsDark(newIsDark)
localStorage.setItem('theme', newIsDark ? 'dark' : 'light')
document.documentElement.classList.toggle('dark', newIsDark)
}
return (
<button onClick={toggle}>
{isDark ? '☀️ Light' : '🌙 Dark'}
</button>
)
}
Using Design Tokens
In Tailwind Utilities
Design tokens automatically become Tailwind utilities:
// Colors
<div className="bg-primary text-foreground">
<button className="bg-accent text-white">
// Typography
<h1 className="text-4xl font-display">
<p className="text-base font-sans">
// Spacing
<div className="p-md space-y-sm">
<section className="mb-xl">
// Border radius
<div className="rounded-lg">
<button className="rounded-full">
// Shadows
<div className="shadow-md">
<div className="shadow-lg hover:shadow-xl">
In Custom CSS
Access tokens via CSS variables:
.custom-component {
background-color: var(--color-primary);
padding: var(--spacing-md);
border-radius: var(--radius-lg);
font-family: var(--font-sans);
transition: all var(--transition-base);
}
.custom-component:hover {
background-color: var(--color-accent);
box-shadow: var(--shadow-lg);
}
Color System Design
OKLCH Color Space (Recommended)
Why OKLCH over RGB/HSL:
- Perceptually uniform (consistent lightness across hues)
- Better for programmatic color generation
- Predictable color mixing
- Accessible contrast calculations
Format: oklch(lightness chroma hue)
- Lightness: 0 (black) to 1 (white)
- Chroma: 0 (gray) to ~0.4 (vivid)
- Hue: 0-360 degrees
@theme {
/* Primary color palette - Blue */
--color-primary-50: oklch(0.95 0.02 250);
--color-primary-100: oklch(0.9 0.05 250);
--color-primary-200: oklch(0.8 0.1 250);
--color-primary-300: oklch(0.7 0.15 250);
--color-primary-400: oklch(0.65 0.18 250);
--color-primary-500: oklch(0.6 0.2 250); /* Base */
--color-primary-600: oklch(0.5 0.2 250);
--color-primary-700: oklch(0.4 0.18 250);
--color-primary-800: oklch(0.3 0.15 250);
--color-primary-900: oklch(0.2 0.1 250);
/* Semantic aliases */
--color-primary: var(--color-primary-500);
--color-primary-hover: var(--color-primary-600);
--color-primary-active: var(--color-primary-700);
}
Semantic Color System
@theme {
/* Base palette */
--color-brand-blue: oklch(0.6 0.2 250);
--color-brand-purple: oklch(0.65 0.25 290);
--color-brand-green: oklch(0.7 0.2 140);
/* Semantic mappings */
--color-primary: var(--color-brand-blue);
--color-secondary: var(--color-brand-purple);
--color-accent: var(--color-brand-green);
/* State colors */
--color-success: oklch(0.65 0.18 140); /* Green */
--color-warning: oklch(0.7 0.2 80); /* Yellow */
--color-error: oklch(0.6 0.22 20); /* Red */
--color-info: oklch(0.65 0.2 230); /* Blue */
/* Surface colors */
--color-background: oklch(1 0 0); /* White */
--color-foreground: oklch(0.2 0 0); /* Near-black */
--color-surface: oklch(0.98 0 0); /* Off-white */
--color-surface-hover: oklch(0.95 0 0);
/* Border colors */
--color-border: oklch(0.9 0 0);
--color-border-hover: oklch(0.8 0 0);
--color-border-focus: var(--color-primary);
/* Text colors */
--color-text-primary: var(--color-foreground);
--color-text-secondary: oklch(0.5 0 0);
--color-text-muted: oklch(0.6 0 0);
--color-text-disabled: oklch(0.7 0 0);
}
Typography System
Type Scale
Modular scale (1.200 ratio recommended for web):
@theme {
/* Type scale using modular scale (1.200 ratio) */
--font-size-xs: 0.694rem; /* 11.11px */
--font-size-sm: 0.833rem; /* 13.33px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.2rem; /* 19.20px */
--font-size-xl: 1.44rem; /* 23.04px */
--font-size-2xl: 1.728rem; /* 27.65px */
--font-size-3xl: 2.074rem; /* 33.18px */
--font-size-4xl: 2.488rem; /* 39.81px */
--font-size-5xl: 2.986rem; /* 47.78px */
--font-size-6xl: 3.583rem; /* 57.33px */
--font-size-7xl: 4.300rem; /* 68.80px */
/* 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-tight: -0.02em;
--letter-spacing-normal: 0;
--letter-spacing-wide: 0.02em;
--letter-spacing-wider: 0.05em;
}
Font Pairings
Avoid generic fonts (Inter, Roboto, Arial)—choose distinctive, memorable fonts that align with brand personality (per frontend-design skill).
@theme {
/* Example: Editorial style */
--font-display: "Clash Display", "Inter Variable", sans-serif;
--font-sans: "Inter Variable", system-ui, sans-serif;
--font-serif: "Merriweather", Georgia, serif;
--font-mono: "JetBrains Mono", "Fira Code", monospace;
/* Font weights */
--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 loading:
// app/layout.tsx (Next.js with next/font)
import { Inter, Merriweather, JetBrains_Mono } from 'next/font/google'
import localFont from 'next/font/local'
const inter = Inter({ subsets: ['latin'], variable: '--font-sans' })
const merriweather = Merriweather({
weight: ['300', '400', '700'],
subsets: ['latin'],
variable: '--font-serif'
})
const jetbrains = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono' })
const clashDisplay = localFont({
src: './fonts/ClashDisplay-Variable.woff2',
variable: '--font-display',
})
export default function RootLayout({ children }) {
return (
<html className={`${inter.variable} ${merriweather.variable} ${jetbrains.variable} ${clashDisplay.variable}`}>
<body>{children}</body>
</html>
)
}
Spacing System
8-point grid (industry standard):
@theme {
/* Spacing scale (8px base unit) */
--spacing-0: 0;
--spacing-0\.5: 0.125rem; /* 2px */
--spacing-1: 0.25rem; /* 4px */
--spacing-2: 0.5rem; /* 8px */
--spacing-3: 0.75rem; /* 12px */
--spacing-4: 1rem; /* 16px */
--spacing-5: 1.25rem; /* 20px */
--spacing-6: 1.5rem; /* 24px */
--spacing-8: 2rem; /* 32px */
--spacing-10: 2.5rem; /* 40px */
--spacing-12: 3rem; /* 48px */
--spacing-16: 4rem; /* 64px */
--spacing-20: 5rem; /* 80px */
--spacing-24: 6rem; /* 96px */
--spacing-32: 8rem; /* 128px */
/* Semantic spacing */
--spacing-xs: var(--spacing-1);
--spacing-sm: var(--spacing-2);
--spacing-md: var(--spacing-4);
--spacing-lg: var(--spacing-6);
--spacing-xl: var(--spacing-8);
--spacing-2xl: var(--spacing-12);
--spacing-3xl: var(--spacing-16);
}
Responsive Breakpoints
@theme {
/* Breakpoints */
--screen-sm: 640px;
--screen-md: 768px;
--screen-lg: 1024px;
--screen-xl: 1280px;
--screen-2xl: 1536px;
}
Usage with media queries:
.container {
padding: var(--spacing-md);
@media (min-width: theme(--screen-md)) {
padding: var(--spacing-lg);
}
@media (min-width: theme(--screen-xl)) {
padding: var(--spacing-2xl);
}
}
Component Tokens
Define component-specific tokens for consistency:
@theme {
/* Button tokens */
--button-height-sm: 2rem;
--button-height-md: 2.5rem;
--button-height-lg: 3rem;
--button-padding-x: var(--spacing-4);
--button-border-radius: var(--radius-md);
/* Input tokens */
--input-height: 2.5rem;
--input-padding-x: var(--spacing-3);
--input-border-width: 1px;
--input-border-radius: var(--radius-md);
--input-border-color: var(--color-border);
--input-focus-border-color: var(--color-primary);
--input-focus-ring-width: 2px;
--input-focus-ring-color: oklch(from var(--color-primary) l c h / 0.2);
/* Card tokens */
--card-padding: var(--spacing-6);
--card-border-radius: var(--radius-lg);
--card-border-color: var(--color-border);
--card-shadow: var(--shadow-sm);
--card-shadow-hover: var(--shadow-md);
}
Animation Tokens
@theme {
/* Durations */
--duration-instant: 0ms;
--duration-fast: 150ms;
--duration-base: 200ms;
--duration-slow: 300ms;
--duration-slower: 500ms;
/* Easing functions */
--ease-linear: linear;
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
/* Combined transitions */
--transition-fast: var(--duration-fast) var(--ease-out);
--transition-base: var(--duration-base) var(--ease-in-out);
--transition-slow: var(--duration-slow) var(--ease-in-out);
}
Integration with frontend-design Skill
Design tokens provide the foundation; frontend-design provides the aesthetic direction.
/* Design tokens define the system */
@theme {
--color-primary: oklch(0.6 0.2 250);
--font-display: "Clash Display", sans-serif;
--spacing-base: 1rem;
}
/* frontend-design skill guides implementation */
/* - Bold aesthetic choices */
/* - Distinctive typography and color palettes */
/* - High-impact animations and visual details */
/* - Context-aware, production-ready code */
When implementing UI:
- Load design-tokens skill for the system
- Load frontend-design skill for aesthetic execution
- Result: Consistent system + distinctive design
Best Practices
Do ✅
- Use semantic names:
--color-primarynot--color-blue-500 - Define once, use everywhere: Single source of truth
- Support dark mode: Plan from the start
- Use OKLCH colors: Better than RGB/HSL
- Follow 8-point grid: For spacing consistency
- Create component tokens: Card, button, input-specific values
- Document your system: Include usage examples
- Reference frontend-design: Let that skill guide aesthetics
Don't ❌
- Don't hardcode values: Always use tokens
- Don't use descriptive names: Semantic intent over implementation
- Don't skip dark mode: Users expect it (2025)
- Don't use generic fonts: Avoid Inter/Roboto/Arial (per frontend-design)
- Don't create too many tokens: Start minimal, expand as needed
- Don't ignore accessibility: Ensure adequate contrast ratios
- Don't forget mobile: Test tokens at all breakpoints
Philosophy
"Design tokens are contracts between design and development."
When design changes (new brand colors, updated spacing), you update tokens—not hundreds of scattered values across components.
Systematic consistency enables creative expression. The design token system provides guardrails; the frontend-design skill provides the artistry within those guardrails.
Start with semantic meaning, not visual appearance. --color-primary adapts to brand changes; --color-blue-500 does not.
When agents design UI systems, they should:
- Use Tailwind 4 @theme directive (not tailwind.config.js)
- Define semantic color tokens (primary, secondary, accent, not blue-500)
- Use OKLCH color space for perceptual uniformity
- Follow 8-point spacing grid
- Create modular type scale (1.200 ratio recommended)
- Support dark mode from the start
- Avoid generic fonts (per frontend-design skill)
- Define component-specific tokens for consistency
- Document the design system with usage examples
- Reference frontend-design skill for aesthetic guidance