~ / mitul
$
Senior Full Stack & AI Engineer
Surat, IN - UTC+05:30
OPEN TO WORK
~30 hrs/wk

Mitul Jagad

Senior Full Stack Developer. Six years shipping production for SaaS and FinTech teams. Lately building AI agents and workflow automations that have to actually run, not just demo.

Resume
FOLLOW
ALL ARTICLESFrontend

Tailwind v4's @theme: one year in

Feb 20265 min
Frontend

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

  1. Run the official upgrade tool - it handles the mechanical bits.
  2. Move each `theme.extend.colors` entry into `@theme` as `--color-<name>`.
  3. Replace any place you accessed tokens from JS (often custom plugins or shadcn-style components) with CSS variable references.
  4. 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.