seedflip
Archive
Mixtapes
Pricing
Sign in

Tailwind v4 Design Tokens: The New @theme Directive Explained

Tailwind v4 replaced the JavaScript config file with a CSS-first approach. The @theme directive is where you define design tokens now. Colors, spacing, fonts, shadows, border radius. Everything that used to live in tailwind.config.js moves into your stylesheet. Yes, the JS config is dead. Here is how the new system works.

Generate Tailwind v4 design tokens →

What @theme does

The @theme directive registers CSS custom properties as Tailwind design tokens. When you write a variable inside @theme, Tailwind creates the corresponding utility classes automatically.

@theme { --color-brand: #6366F1; --color-surface: #FAFAFA; }

This gives you bg-brand, text-brand, border-brand, bg-surface, and every other color utility. The variable name after the namespace prefix becomes the utility name.

Namespace conventions

Tailwind v4 uses the variable prefix to determine what type of token you're defining. This is the type system. Get the prefix right and Tailwind generates the correct utilities automatically.

@theme { /* Colors → bg-*, text-*, border-*, ring-*, etc. */ --color-primary: #6366F1; --color-primary-light: #818CF8; /* Spacing → p-*, m-*, gap-*, w-*, h-*, etc. */ --spacing-18: 4.5rem; --spacing-128: 32rem; /* Font families → font-* */ --font-display: "Inter", sans-serif; --font-mono: "Geist Mono", monospace; /* Font sizes → text-* */ --text-2xs: 0.625rem; /* Border radius → rounded-* */ --radius-pill: 9999px; /* Shadows → shadow-* */ --shadow-soft: 0 2px 8px rgba(0,0,0,0.08); /* Animations → animate-* */ --animate-fade-in: fade-in 0.3s ease-out; }

The full list of recognized prefixes: --color-, --spacing-, --font-, --text-, --radius-, --shadow-, --animate-, --inset-shadow-, --drop-shadow-, --blur-, --breakpoint-, --tracking-, --leading-, --ease-, and more.

Why CSS instead of JavaScript

Three practical reasons:

1. Colocation. Your design tokens live in the same file as your base styles, custom properties, and dark mode overrides. One file, one source of truth.

2. Dynamic values. CSS variables in @theme can reference other variables, use calc(), and participate in the cascade. A JavaScript object can't do that without build-time processing.

3. No build dependency. The CSS file works with any bundler. No PostCSS plugin version mismatches, no config file format differences between ESM and CJS. Just CSS.

If you suspected the JS config was always an awkward fit for what is fundamentally a styling concern, you were right. The @theme directive is the correction.

Referencing CSS variables in @theme

This is where it gets powerful for component libraries like shadcn/ui. You can define semantic CSS variables in :root and reference them in @theme:

/* Semantic tokens (change with theme/dark mode) */ :root { --background: 0 0% 100%; --foreground: 240 10% 3.9%; --primary: 239 84% 67%; } .dark { --background: 240 10% 3.9%; --foreground: 0 0% 98%; --primary: 239 90% 71%; } /* Register as Tailwind tokens */ @theme { --color-background: hsl(var(--background)); --color-foreground: hsl(var(--foreground)); --color-primary: hsl(var(--primary)); }

Now bg-background resolves to white in light mode and near-black in dark mode. The @theme block is static. The :root and .dark blocks handle the dynamic switching. Clean separation of concerns.

Extending vs overriding the default theme

By default, @theme extends the built-in Tailwind theme. Your custom tokens are added alongside the defaults. If you want to replace a namespace entirely:

/* Override ALL colors (removes default palette) */ @theme { --color-*: initial; /* Now define only your colors */ --color-background: #FFFFFF; --color-foreground: #09090B; --color-primary: #6366F1; }

The --color-*: initial syntax clears the entire color namespace. Use this when you want a completely custom palette with no Tailwind defaults leaking through. This is ideal for branded design systems where you want full control.

Nested token scales

You can create scales just like the default palette by using dashes:

@theme { --color-brand-50: #EEF2FF; --color-brand-100: #E0E7FF; --color-brand-200: #C7D2FE; --color-brand-300: #A5B4FC; --color-brand-400: #818CF8; --color-brand-500: #6366F1; --color-brand-600: #4F46E5; --color-brand-700: #4338CA; --color-brand-800: #3730A3; --color-brand-900: #312E81; --color-brand-950: #1E1B4B; }

This generates bg-brand-50 through bg-brand-950, matching the pattern you already know from the default palette.

A complete SeedFlip example

Here is what a SeedFlip design seed looks like exported as a Tailwind v4 @theme block:

/* SeedFlip "Amethyst" seed — Tailwind v4 export */ @theme { --color-background: #FAFAFA; --color-foreground: #1A1A2E; --color-primary: #6C5CE7; --color-primary-foreground: #FFFFFF; --color-secondary: #F0EDFF; --color-accent: #A29BFE; --color-muted: #F4F4F5; --color-border: #E4E4E7; --font-display: "Plus Jakarta Sans", sans-serif; --font-mono: "JetBrains Mono", monospace; --radius-sm: 0.375rem; --radius-md: 0.5rem; --radius-lg: 0.75rem; --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.06); --shadow-elevated: 0 4px 12px rgba(0, 0, 0, 0.08); }

Paste that into your CSS, and you have a complete design system. Colors, fonts, radii, and shadows. All registered as Tailwind utilities. No config file. No build step. Just CSS.

Migration from tailwind.config.js

The mapping is direct:

/* JS config key → @theme variable */ colors.brand → --color-brand spacing.18 → --spacing-18 fontFamily.display → --font-display fontSize.2xs → --text-2xs borderRadius.pill → --radius-pill boxShadow.soft → --shadow-soft animation.fadeIn → --animate-fade-in

For the full breakdown of what changed across Tailwind v3 and v4, see Tailwind v4 Color System. For how tokens map to different config approaches, read Tailwind Config Design Tokens. And if you're still wondering what tailwind.config.js even was, start with What is Tailwind Config.


The @theme directive is the single best change in Tailwind v4. It makes design tokens a CSS concern, not a build tool concern. If you're building a new project, start here. If you're migrating, the conversion is mechanical. Either way, your design system lives in one place now. SeedFlip exports design tokens in the exact format @theme expects. 104 curated seeds, ready to paste.

Ready to stop guessing?

One flip. Complete design system. Free CSS export.

Generate Tailwind v4 design tokens →