Confession: I put off the v4 upgrade for four months. Not because v4 looked bad. I read the announcement post the day it dropped. It was because I had a Next.js app in production that leaned on maybe fifteen Tailwind plugins, half of which hadn’t been touched since 2023. I told myself I’d wait for “the ecosystem to catch up,” which is the excuse I use for every framework upgrade I don’t want to do.
Then last weekend I finally sat down and did it. Two projects, a lot of coffee, roughly one Saturday. Most of it went fine. But there were about six things that quietly broke in ways the upgrade tool didn’t catch, and I want to write those down before I forget them.
If you’re still on v3 and eyeing the official upgrade guide, treat this as the sidecar post: what actually bit me, in the order it bit me.
The @tailwind directives are gone, and yes it matters
The first thing v4 does is rip out the three lines every Tailwind project starts with. In v3 my globals.css had:
@tailwind base;
@tailwind components;
@tailwind utilities;
In v4, that whole block collapses into a single import:
@import "tailwindcss";
The upgrade CLI handled this fine on both projects. What it didn’t handle was the fact that I had a custom CSS layer sitting between @tailwind base and @tailwind components where I patched a few resets. When it collapsed everything into one import, my layer ordering flipped and my custom reset ran after Tailwind’s, which meant my body font-family got clobbered on the marketing site.
Fix: put overrides inside an explicit @layer block so ordering is deterministic.
@import "tailwindcss";
@layer base {
body { font-family: "Inter Variable", system-ui, sans-serif; }
}
Ten minutes of debugging that could have been zero if I’d read one paragraph of the migration guide. The official v4 announcement is worth reading before you touch anything.
The config file didn’t move, it dissolved
The bigger conceptual change is that tailwind.config.js is optional in v4. Theme tokens live in CSS now, inside a @theme block. I wrote a whole separate post about the Tailwind CSS v4 config file I deleted so I won’t relitigate the philosophy here, but the migration shape looks like this.
Before, in tailwind.config.js:
module.exports = {
theme: {
extend: {
colors: {
brand: {
50: "#f4f8ff",
500: "#3358ff",
900: "#0a1a5a",
},
},
fontFamily: {
display: ["Cabinet Grotesk", "sans-serif"],
},
},
},
};
After, in globals.css:
@import "tailwindcss";
@theme {
--color-brand-50: #f4f8ff;
--color-brand-500: #3358ff;
--color-brand-900: #0a1a5a;
--font-display: "Cabinet Grotesk", sans-serif;
}
The mapping is: theme.extend.colors.brand.500 becomes the CSS variable --color-brand-500, and Tailwind generates bg-brand-500, text-brand-500, and the rest from it. Same for fontFamily.display → --font-display → font-display.
Two things bit me here.
First, I had a JS-based color palette generator wired into my old config (chroma.js, do not ask). That doesn’t work anymore because there’s no JS execution step in v4’s config path. I ended up writing a small Node script that computes the color ramp once at build time and emits a theme.css file that I @import before the main Tailwind import.
Second, I lost my plugins: [require("@tailwindcss/typography")] line and forgot to add it back. Prose styles just weren’t there. The v4 way is to add @plugin inside the CSS: @plugin "@tailwindcss/typography";. Another five minutes of “why is my blog body ugly again.”
The utility renames that quietly changed my design
This is the one that made me swear at my laptop.
In v3, shadow-sm was a subtle shadow and shadow was medium. In v4 the scale shifted: what was shadow-sm is now shadow-xs, and what was plain shadow is now shadow-sm. Same story for parts of the blur, rounded, and outline scales.
The upgrade CLI rewrites most of these in your source files. It does not rewrite them inside strings that get concatenated at runtime. My admin dashboard had a component like this:
const size = compact ? "sm" : "md";
return <div className={`shadow-${size} rounded-${size}`}>...</div>;
The CLI can’t statically see shadow-${size}, so it left it alone. Result: my dashboard’s card shadows got heavier overnight because shadow-sm in v4 renders like v3’s shadow. Nothing crashed. Nothing threw a warning. The design just drifted.
Fix: I scanned my codebase for string interpolation in className values and switched to a static lookup table. In hindsight I should never have been doing runtime className concatenation for design tokens, but v3 was permissive and I got lazy.
If you’re doing this at scale, grep -rn 'className={[^]*${' src/ is a decent first pass to find the interpolations you actually need to fix. It’ll miss some (template literals split across lines) but it catches the obvious ones.
PostCSS is out, Vite is in, npm scripts change
On the smaller of my two projects I was using PostCSS with a hand-written config. v4 ships a proper Vite plugin, and Tailwind’s team basically wants you to switch to it if you’re on Vite. The migration looks like this.
Before:
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
After:
// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
export default {
plugins: [tailwindcss()],
};
Build times on this project dropped from about 4.1s to 1.6s on a cold build, which lined up roughly with what I saw when I first tried the Oxide engine on its own. Not a placebo, not sponsored, just faster.
The Next.js project was slightly more annoying because Next uses its own PostCSS pipeline. There the answer is the PostCSS plugin (@tailwindcss/postcss), which is supported and works fine. If you’re on Next 15+ you don’t need to touch anything except swap the plugin name.
Browser support is now a real conversation
v4 drops IE 11 (obviously) but also raises the floor on modern browsers. It leans on @property, cascade layers, color-mix(), and container queries. All of those need Safari 16.4+, Firefox 128+, and Chromium 111+.
For my marketing site, our analytics said 0.4% of traffic was on browsers below that line, so I moved on. For the admin app, I checked with the ops team and we’re on evergreen Chrome only, so also fine. If you have a compliance client on locked-down browsers, this is the one thing worth verifying before you upgrade. Tailwind is explicit that v3 stays supported for anyone who needs to hold the line.
Under the hood, v4 uses Lightning CSS instead of PostCSS + autoprefixer, which is part of why the build is so much faster and also part of why old browser targets don’t work. Lightning CSS decides what to prefix based on your browserslist, and Tailwind’s baseline is stricter than autoprefixer’s default.
The dev experience is quietly a lot nicer
I want to close with the thing that surprised me most, because it’s the part the release notes undersell.
The dev server just feels faster. Not “here’s a benchmark faster.” Subjectively faster in the loop. HMR on a Tailwind class change was noticeable in v3, and it’s basically instant in v4. Combined with the CSS-based config, my whole loop is: edit a class, save, see it. No config file to remember. No restart when I change theme tokens. The build step stopped feeling like a thing.
The other quiet win is that container queries are first-class now. I’d been half-heartedly using them via a plugin in v3 and it always felt bolted on. v4 makes them the default way to think about component-level responsiveness, which is a shift I wrote about in more detail in the container queries post. If you’re building any kind of dashboard or component library, that one change alone justifies the upgrade for me.
I keep a running log of these kinds of migration notes on my work page because I refer back to them more often than I’d like to admit.
What to actually do this week
If you’re on v3 and thinking about it: pick your smallest project, run the CLI, and just do it. You’ll hit two or three of the things above, they’ll all be fixable in an afternoon, and you’ll come out with a faster build and a config file that lives where it should have lived all along.
If you’re on v3 and can’t move (design system frozen, third-party plugin stuck, weird browser requirement), stay on v3. It’s not going anywhere and Tailwind’s maintenance policy is generous. The upgrade will still be there when your constraints change.
Either way, do yourself a favor and read the upgrade guide end-to-end before you start. I skipped that step and it cost me an hour I could have spent doing almost anything else.