Tailwind v4 shipped in late 2024 and many people's first reaction was "why did they change what worked?". After migrating three VantLabs projects, my reaction is "I wish they'd changed more". Here's the real migration, no marketing.
The strangest: goodbye tailwind.config.js
In v3, all your config lived in tailwind.config.js. Theme, plugins, content paths, presets. A familiar JavaScript file any dev could read.
In v4, that file doesn't exist. Config moved to an @theme inline block inside your CSS:
@import "tailwindcss";
@theme inline {
--color-accent-500: oklch(76% 0.230 132);
--font-display: "Bricolage Grotesque", system-ui;
--radius-lg: 20px;
}First reaction is "this feels weird". But after two weeks, my conclusion is that it's better. The reasons:
- Your palette lives in native CSS variables, not JavaScript. That means you can use them from plain CSS, from inline styles, from other libraries that expect CSS variables. One source of truth.
- The build doesn't need to execute JavaScript to resolve the theme. It's faster and more predictable.
- Color-mix, oklch, container queries, and other modern CSS features work natively without transformations, because you live in CSS from the start.
What hurt
1. Some utilities changed names. flex-shrink-0 still works, but shrink-0 is now canonical. overflow-ellipsis is text-ellipsis. There's an automatic codemod but it covers 80% — the remaining 20% you hunt by hand.
2. v3 plugins that depended on the JS config don't work. @tailwindcss/typography in particular has no official equivalent in v4 at the time I migrated. I had to write my own Prose system with descendant selectors, documented in my code at components/blog/prose.tsx. Works fine, but it was extra work.
3. Cascade layers changed debugging. Tailwind v4 uses cascade layers more strictly: @layer theme, base, components, utilities. If you write CSS outside any layer (which we all did in v3), you enter the "implicit outer layer" which has HIGHER priority than utilities. That caused a p { margin: 0 } of mine to silently squash every mb-* applied to paragraphs. Three days of debugging cost me that lesson.
What got noticeably better
Native OKLCH. In v3 you could use oklch in your config, but generated utilities were HSL conversions. In v4 utilities are genuine oklch. That gives me perceptually correct contrast and dark-mode mixing that in HSL was impossible without tricks. It's why I can have a bright lime palette with dark text that's automatically accessible.
First-class container queries. In v4 they ship built-in with direct syntax: @container in JSX. Before you needed the plugin. Small change, but less friction.
Faster builds. In medium projects I noticed builds 30-40% faster. For a small project you don't notice. For a large one with many components, you do.
Fewer files in the repo. Goodbye tailwind.config.js, goodbye postcss.config.js in some cases (v4's is minimal: just loads @tailwindcss/postcss). Satisfying.
How long it took
For the three projects:
- vantlabs.io (corporate): 4 hours. Small and new, so no tech debt.
- ReclamaAI (web): 12 hours. More components, more exotic utilities, typography plugin to replace.
- VantFi backend admin: 2 hours. Minimal frontend.
Total: ~18 real hours. Adds up. But the ROI is positive from day two: the code is cleaner, the theme is more portable, the build is faster. For projects you expect to maintain for years, it's worth it.
What I would NOT migrate yet
If you're working on a critical project years in production that heavily depends on typography, forms, or aspect-ratio plugins, wait. Some plugins are migrated, others aren't yet. Verify first that your full stack has v4 equivalents before starting the migration.
For new projects, instead: start directly in v4. The learning curve of the @theme inline block is a couple of hours, and everything you learn serves you for the next years of Tailwind.