Tailwind v4's @theme: one year in
Tailwind v4's @theme: one year in
The CSS-first @theme block was the biggest shift in Tailwind's history and the documentation barely sells it. After a year of building real apps with it, here's what changed and why I'm not going back.
Tailwind v4 dropped the `tailwind.config.js` file in favour of a CSS-first `@theme` block. After a year of using it on production sites, it's the single best change Tailwind has made - and most teams I talk to still don't realise how much it simplifies things.
What changed in one sentence
Your design tokens - colours, fonts, spacing, shadows - now live as CSS variables inside an `@theme` block in your CSS, not as a JavaScript object in a config file.
What that actually means in code
@import "tailwindcss";
@theme {
--color-paper: #f0f2f5;
--color-ink: #1a1f2c;
--color-primary: #4770ff;
--font-sans: "Instrument Sans", ui-sans-serif, system-ui;
}Now `bg-paper`, `text-ink`, `bg-primary`, and `font-sans` are utility classes - and the variables themselves are real CSS custom properties you can also reference directly in styles, JS, or third-party components.
Why this matters
Themes become trivial
Multi-theme sites used to mean either rebuilding Tailwind with different configs or overriding utility classes by hand. Now you set `--color-primary` to one value at `:root` and another at `[data-theme="dark"]`, and every `bg-primary` in the app re-tints automatically. No re-build. No `dark:` prefix proliferation. Just CSS variables doing what they were designed to do.
Third-party libraries finally fit
Any library that accepts a CSS variable for a colour can now be wired into your design tokens directly - no token-mapping shim, no theme-provider gymnastics.
Less config noise
A 200-line `tailwind.config.js` extending the default theme, declaring custom plugins, and exporting types collapses into a 30-line `@theme` block in the CSS file you were already going to edit.
What's lost
If you were doing heavy programmatic theme generation in JavaScript (e.g. computing token scales from a base value), v4's @theme is less ergonomic. You can still do it - the tokens are just CSS, and CSS now has `color-mix()` and arithmetic - but if your design system was built around computed JS objects, the migration takes some thinking.
How to migrate
- Run the official upgrade tool - it handles the mechanical bits.
- Move each `theme.extend.colors` entry into `@theme` as `--color-<name>`.
- Replace any place you accessed tokens from JS (often custom plugins or shadcn-style components) with CSS variable references.
- Delete `tailwind.config.js`. You won't need it back.
I migrated three production sites to v4 over the last year. None of them have been moved back. The CSS-first model is genuinely better for the way design systems are built today.