Headline: Tailwind v4 is the most significant rewrite since the framework launched — CSS-first config, Lightning CSS under the hood, container queries built-in, and no more
tailwind.config.js. I migrated two production projects and here's what actually broke and what the upgrade tool misses.
Tailwind CSS v4 arrived with a steeper upgrade curve than most version bumps in the JS ecosystem. The configuration story changed completely. The build engine changed. Several features that previously required plugins are now built-in.
The headline change: no more tailwind.config.js
In v3, configuration lived in a JavaScript file — theme extensions, plugins, content paths. In v4, it moves into your CSS:
@import "tailwindcss";
@theme {
--color-brand: #6366f1;
--spacing-18: 4.5rem;
}
Theme tokens become CSS custom properties under @theme, and Tailwind generates utility classes automatically. The content array is gone — v4 detects source files automatically.
The new engine: Lightning CSS
Tailwind v4 ships with Lightning CSS replacing PostCSS as the default:
- Build times drop significantly (cold rebuild went from ~8s to under 3s on the dashboard)
- CSS nesting works natively without a plugin
- Modern CSS features like
color-mix(),@starting-style, oklch are transpiled automatically -
autoprefixeris no longer needed
New features built-in
Container queries — native in v4, no plugin needed:
<div class="@container">
<div class="grid grid-cols-1 @sm:grid-cols-2">...</div>
</div>
3D transforms — rotate-x-45, rotate-y-12, perspective-1000 for card flip effects without inline styles.
Dynamic spacing — p-13, mt-22 work without explicit definition.
Migration: the upgrade tool and what it misses
npx @tailwindcss/upgrade@next
The codemod handles the mechanical parts. What it missed:
-
Custom plugins — the JS plugin API changed; non-trivial v3 plugins need a rewrite to the new
@plugin/@utilityAPI -
theme()calls in CSS — replacetheme('colors.zinc.900')withvar(--color-zinc-900); grep fortheme(after the tool runs -
@applyin third-party components — test after migrating; the underlying CSS variable names changed - JIT-era patterns — some peer/group modifier edge cases needed manual review
Build tool integration
// vite.config.ts
import tailwindcss from '@tailwindcss/vite';
export default { plugins: [tailwindcss()] };
Remove PostCSS config after adding the Vite plugin. For Next.js + Turbopack: the integration is still maturing — test on a branch first.
The checklist
- Run the upgrade tool on a clean branch; commit it separately from manual fixes.
- Audit custom plugins first — they're the largest time sink.
- Grep CSS files for
theme(and replace withvar(--...). - Install
@tailwindcss/vite, remove PostCSS config. - Visual regression pass on dark mode and
@applyusage. - Check third-party component library changelog for v4 notes.
Both migrations took a full afternoon. The result: leaner CSS, faster builds, and a config story that finally makes sense.
More writing on eng-ahmed.com. Built by Devya Solutions.
Top comments (0)