Tailwind v4 is the largest rewrite the framework has had. The headline is a Rust-based engine called Oxide, but the more consequential change is philosophical: configuration moves out of JavaScript and into CSS, the framework leans hard on native browser features that didn't exist when v3 shipped, and a pile of v3 scaffolding gets quietly retired. For most production codebases the migration is measured in hours, not days — but the gotchas are real and a few of them will bite you if you don't know they exist. Here's what changed, what's worth the upgrade, and the checklist that keeps the v3-to-v4 jump from eating a sprint.
The Oxide engine, in one paragraph
Oxide is a ground-up rewrite of Tailwind's core in Rust, replacing the PostCSS-based JavaScript pipeline that powered v3. The numbers from Tailwind Labs are aggressive — full builds over 3.5× faster, incremental builds over 8× faster, and the no-op rebuild path up to 100× faster. On a real Next.js app with a few thousand classes, that translates to median incremental rebuilds dropping from the 40–50ms range into single-digit milliseconds. HMR for style-only edits often lands faster than React can remount. The practical win isn't the benchmark number — it's that Tailwind stops being the slow part of your dev loop.
CSS-first configuration with @theme
The biggest day-to-day change is that tailwind.config.js is no longer the source of truth. In v4, design tokens live inside your CSS file under an @theme block, expressed as native CSS custom properties. Tailwind reads that block at build time to generate utility classes, and the same variables are available at runtime for JavaScript, inline styles, and arbitrary CSS. One file, one source of truth, no more JS-to-CSS token drift.
/* app/globals.css — Tailwind v4 */
@import "tailwindcss";
@theme {
--color-brand-500: oklch(0.72 0.18 250);
--color-brand-600: oklch(0.64 0.19 250);
--font-display: "Inter", ui-sans-serif, system-ui, sans-serif;
--spacing-18: 4.5rem;
--radius-xl: 1rem;
--breakpoint-3xl: 1920px;
}
/* Utility classes bg-brand-500, text-brand-600, font-display,
p-18, rounded-xl, and the 3xl: variant are now available. */v3 to v4, side by side
| Concern | Tailwind v3 | Tailwind v4 |
|---|---|---|
| Config location | tailwind.config.{js,ts} | @theme block in your CSS entry |
| Entry point | @tailwind base; @tailwind components; @tailwind utilities; | @import "tailwindcss"; |
| Content detection | Explicit content: [] globs | Automatic scan honouring .gitignore |
| Token format | JS objects (theme.extend.colors) | CSS variables (--color-*) |
| Color space | sRGB (hex, hsl) | OKLCH by default |
| Container queries | Plugin (@tailwindcss/container-queries) | Built in (@container utilities) |
| Cascade layers | Simulated via @layer directive | Real @layer rules in output |
| Build engine | PostCSS + JIT (JavaScript) | Oxide (Rust) |
tailwind.config.js still works in v4 via the @config directive, so you can migrate in two phases: bump to v4 with your existing JS config, then move tokens to @theme once the upgrade is green. Most teams split it that way.
Native browser features, finally
Tailwind v4 assumes a modern browser baseline and leans on platform features v3 had to polyfill or ignore. Three are worth calling out.
- Native cascade layers. v4 emits real @layer theme, @layer base, @layer components, @layer utilities blocks. That means predictable specificity without !important stacks and cleanly overrideable styles from app code — an actual fix for the specificity wars that plagued large v3 projects.
- Container queries as a first-class citizen. The @container utility and @sm:, @md:, @lg: variants ship in core. Components can respond to their parent's size instead of the viewport, which is what you actually want inside dashboards, sidebars, and marketplace cards.
- Registered custom properties via @property. Color and spacing tokens get typed, which means transitions on CSS variables now animate instead of jumping — a small quality-of-life win that shows up everywhere.
What got dropped — and why most teams won't notice
The JIT (just-in-time) label is gone because JIT is now the only mode. Several v3 artifacts go with it. The content array is no longer required; Oxide auto-detects sources by scanning the project tree and honouring .gitignore. Deprecated opacity utilities (bg-opacity-50, text-opacity-50) are removed in favour of the slash syntax (bg-black/50). The default border colour is now currentColor instead of gray-200, which will silently re-skin borders unless you set it explicitly. The default ring width drops from 3px to 1px. A few utilities changed names (shadow-sm is now shadow-xs, blur-sm is now blur-xs). These are small individually; collectively they show up as visual diffs after the upgrade.
Plugin compatibility is the sharpest edge. Any v3 plugin that reached into the internals — custom variants, arbitrary JIT hooks, the resolveConfig() helper — will likely break. First-party plugins (typography, forms, aspect-ratio) have v4 releases; many community plugins lag. Audit your plugin list before the upgrade, not during.
The migration checklist
The official upgrade tool (npx @tailwindcss/upgrade) handles the mechanical parts of the transition. The tool assumes a relatively clean v3 setup; anything non-standard — custom plugins, complex theme overrides, JS-driven class generation — needs manual review. This is the sequence that's worked cleanly on production codebases we've shipped through the upgrade.
- Freeze feature work on a dedicated branch and get a green build on v3 before starting. A passing baseline is the only way to tell whether a post-upgrade diff is intentional.
- Run npx @tailwindcss/upgrade at the repo root. The tool updates package.json, rewrites imports, converts the config where it can, and flags anything it can't handle.
- Switch the build plugin. Swap @tailwindcss/postcss for @tailwindcss/vite (or keep postcss if you're on webpack/Next.js). Vite users get the best incremental experience.
- Replace @tailwind base/components/utilities with a single @import "tailwindcss"; at the top of your CSS entry.
- Audit your tailwind.config.js. Token definitions (colors, spacing, fonts) port cleanly to @theme. Content paths can usually be deleted. Plugins move to the @plugin directive inside CSS. Anything that referenced theme() in JS or used resolveConfig() needs rethinking.
- Run the app. Visual diffs will surface first — border colours going currentColor, ring widths shrinking, any utility rename missed by the codemod. Fix them in one pass with a grep-and-replace rather than scattered edits.
- Re-verify dark mode, prefers-reduced-motion, and any RTL work. Variant order occasionally shifts in v4; a quick visual pass on those states catches 90% of the regressions.
- Remove legacy shims. Once the build is green, delete the @config directive if you moved tokens to @theme, drop any postcss.config.js entries Oxide now handles, and clean up the transitional alias layer.
The OKLCH default is worth understanding
v4 ships its default palette in the OKLCH color space instead of sRGB. OKLCH is perceptually uniform — equal numerical steps look like equal visual steps — which is why the default palette in v4 has noticeably more consistent contrast across hues than v3 did. The practical implication is twofold. First, if you define custom brand colors in hex and mix them with v4's defaults, you'll get palettes that don't sit quite right next to each other. Define brand tokens in OKLCH too. Second, OKLCH is a Baseline browser feature but isn't in every evergreen install yet; if your analytics show meaningful traffic on older Safari or Firefox, either stay on sRGB for brand tokens or supply an sRGB fallback via color-mix().
What to rebuild — and what to leave alone
Every major upgrade is an invitation to rewrite things you shouldn't. Resist. The components that worked on v3 will mostly work on v4 with zero changes beyond the rename pass. Rebuild only where v4 adds a feature that replaces a hack: container-query logic that used to rely on JavaScript resize observers, theme switching that was implemented with provider gymnastics, or specificity workarounds that involved !important chains. Those pay back the effort. Everything else — your card components, your form layouts, your dashboard grids — stay as they are. The upgrade ships value fastest when you treat it as infrastructure, not a redesign.
If you run a monorepo with multiple Tailwind consumers (design-system package, web app, marketing site), migrate the leaf apps first and the shared design-system package last. The reverse order forces you to keep v3 and v4 compatibility in the shared layer for longer than necessary.
Should you upgrade now
For greenfield projects, v4 is the default — there's no reason to start a new build on v3. For existing production apps, the answer is almost always yes, but the timing depends on plugin exposure. A vanilla Tailwind + first-party-plugins codebase is a one-afternoon migration. A codebase with four community plugins, a custom JIT extension, and three designers co-owning the token file is a week of coordination. The one group that should wait is teams with heavy dependency on v3-only community plugins that haven't been ported; the ecosystem is closing that gap, but a few corners are still catching up.
Migrating a single app to v4 is a tactical improvement. Migrating your shared design system to v4 is a strategic one — everything downstream inherits the faster build, the real cascade layers, and the single-source-of-truth token model. Prioritise the design system even if the first consumer app lags a cycle behind.
Key takeaways
- Oxide makes Tailwind stop being the slow part of your dev loop. The benchmark numbers are real and you'll feel them first through HMR.
- Tokens belong in CSS now. @theme replaces tailwind.config.js for design tokens, and CSS custom properties become the single source of truth across styling and runtime code.
- Automatic content detection, native cascade layers, and first-class container queries remove real v3 pain points. You no longer fight the tooling to get modern CSS.
- Plugin compatibility is the sharpest migration edge. Audit before you upgrade; don't let a missing plugin block a green build halfway through.
- Use the official upgrade tool for the mechanical pass, then reserve a review cycle for visual diffs — border colours, ring widths, and renamed utilities account for most of the surprises.