Tailwind v4 changes how the framework is configured. Your tailwind.config.js is gone, and the theme now lives in CSS. The engine is faster and setup is simpler, but a handful of renames will break your build on the first run — so here’s the migration in the order that actually works.
Config lives in CSS
You pull in Tailwind with one import and define your theme inside an @theme block. Every token becomes a real CSS variable, so --color-brand is readable in devtools and at runtime. Your design tokens and your styles finally share one source instead of hiding in a JS file nothing else can see.
@import "tailwindcss";
@theme {
--color-brand: oklch(0.68 0.19 264);
--font-display: "Cal Sans", sans-serif;
--radius-card: 0.75rem;
}There’s no content array to maintain either — v4 scans your template files automatically, and you only reach for @source when it can’t find one. Folding that scan into the engine is a big part of why v4 builds noticeably faster.
What actually breaks
The renames are the tripwire. shadow-sm is now shadow-xs, outline-none becomes outline-hidden, and the old @tailwind directives are replaced by a single import. Spacing, ring, and border defaults shifted too, so expect a few pixels to move.
The PostCSS plugin also moved to its own package, @tailwindcss/postcss. Don’t fix any of this by hand — the official codemod does it for you, and it runs cleanest on a branch with nothing else in flight.
Migration order
Run it in this sequence, on a fresh branch you can throw away if it goes sideways. Skipping a step is how people end up debugging a half-migrated build.
- Upgrade to Node 20+ and set a modern browser target.
- Run
npx @tailwindcss/upgradeand read every line of the diff. - Move your
theme.extendvalues into an@themeblock. - Delete
tailwind.config.jsonce nothing imports it.
When to wait
Migrate now if you own your styles and target current browsers. The payoff is a faster build and a config you can actually read, and most apps clear that bar today. A theme you can read at a glance is worth the afternoon by itself.
Hold off if a critical plugin hasn’t shipped a v4 build, or you still support older engines. v4 leans on @property and oklch(), which old browsers silently ignore, so check your own analytics before committing.