Migrating to Tailwind CSS v4: The Oxide Engine, CSS-First Config, and a Faster Build
By Arash Latifi
Tailwind v4 drops the JavaScript config for a CSS-first @theme block and ships a Rust-based Oxide engine that builds ~5x faster. Step-by-step v3→v4 migration guide with comparison tables, code, and pitfalls.
TL;DR: Tailwind v4 swaps its engine for Oxide (written in Rust), making full builds up to 5x faster and incremental builds over 100x faster. The big change for you:
tailwind.config.jsis gone — configuration now lives in CSS via the@themeblock. Utility class names barely changed, so the migration is mostly about plugins and config, not HTML. You can finish it in ~10 minutes.
When Tailwind v4 dropped, most of us assumed "just another version bump." It isn't. The configuration philosophy flipped. If you're still on v3, this one's for you — metaphor and code, no jargon.
Why Upgrade at All? — Before/After
Oxide isn't "a bit faster." It's an order of magnitude different. Real numbers from migrated projects:
| Metric | Official Tailwind claim | Caveat | |---|---|---| | Full build | up to 5x faster | varies with project size and complexity | | Incremental build | over 100x faster | only for classes actually used | | CSS size / memory | no official figure published | depends entirely on project structure |
These are Tailwind's own published numbers from the upgrade guide. Measure your own project instead: time pnpm build before and after.
The analogy is simple: v3 re-ran a whole workshop each time to count which classes were used; v4's Rust engine does that counting in milliseconds. On a big project, the "hit F5 and wait" feeling is gone.
The Roadmap: 6 Steps
Step 1 — Update dependencies
If you use @tailwindcss/vite, remove the old plugin from vite.config.ts and add this:
Step 2 — Swap the @tailwind directives
Step 3 — Move config into CSS (the big one!)
Your custom color lived in a JS object in v3:
In v4 it becomes real CSS — via the @theme block:
Two things to note:
- Names are flattened: old
colors.brand.lightis now--color-brand-light, not--color-brand.light. - These are real CSS variables: usable outside Tailwind — in inline styles, arbitrary values, even
getComputedStylein JS.
Step 4 — Load plugins with @plugin
Step 5 — Fix dark mode
In v3 you wrote darkMode: "class". v4 defaults to prefers-color-scheme; for class strategy:
Step 6 — Declare non-standard paths with @source
Content outside default scan paths (e.g. a file in another package):
Quick Comparison: What Changed?
| Topic | v3 | v4 |
|---|---|---|
| Engine | PostCSS + JS | Rust (Oxide) |
| Config | tailwind.config.js | CSS @theme |
| Plugins | require in config | @plugin in CSS |
| Custom classes | @layer utilities | @utility |
| Container Queries | separate plugin | built-in (@container) |
| corePlugins | available | removed |
| theme() | JS function | use CSS vars |
| Utility class names | — | almost unchanged |
Pitfalls You'll Hit
1. @layer utilities no longer works. Your custom class used to live inside @layer utilities so it picked up variants like hover: and lg:. In v4 use @utility:
2. Drop the theme() function. Instead of theme("colors.primary"), use var(--color-primary).
3. Third-party plugins, not you, are the risk. Real-world migrations show the cost lives in plugins, not templates. Audit your plugin list for v4-compatible versions first — one stale plugin breaks the rest.
4. Nested color objects. If you had colors.brand.light, flatten it: --color-brand-light.
Migration Checklist
- [ ] Baseline recorded: run
npm run buildand note the CSS size and build time before touching anything - [ ] Upgrade path chosen:
npx @tailwindcss/upgradeon a new branch, or the manual steps below - [ ]
@tailwind base/components/utilitiesreplaced with a single@import "tailwindcss"; - [ ] Every theme value from
tailwind.config.jsmoved into an@themeblock as CSS variables - [ ] Plugins loaded with
@plugin, and each one confirmed to ship a v4-compatible release - [ ]
postcss-importandautoprefixerremoved from the PostCSS config if they were only there for Tailwind - [ ] Browser support confirmed against Safari 16.4+, Chrome 111+, and Firefox 128+
- [ ] A build diff reviewed in the browser, watching for the renamed
shadow-*,rounded-*, andblur-*scales
FAQ
Q: Will my current classes (flex, text-sm, bg-red-500) break?
No. Utility names are nearly unchanged; most v3 templates compile under v4 with zero HTML edits. The real work is the config file and the plugins. The renamed scale utilities are the exception: shadow-sm became shadow-xs, shadow became shadow-sm, and the same pattern applies to rounded-* and blur-*.
Q: Can I run v3 and v4 side by side?
Not cleanly. v4 is a deliberate breaking change; migrate in one shot. For very large projects a staged migration with @source is possible but more painful.
Q: Is tailwind.config.js fully gone?
It is still supported for backward compatibility, but it is no longer detected automatically in v4 — you have to load it explicitly with @config "../../tailwind.config.js". The recommended path is CSS-first, with design tokens as CSS variables.
Also worth keeping in mind:
- Why the CSS output shrinks: the new engine drops the old overhead, and because the theme is emitted as CSS variables, duplication disappears.
- Next.js: on Next.js 15+ use
@tailwindcss/postcss—@import "tailwindcss"in your main CSS file and the PostCSS plugin inpostcss.config.mjs.
Wrap-up
Migrating to Tailwind v4 means accepting a big philosophical shift: config moved from JavaScript to CSS, and the engine moved from JavaScript to Rust. The payoff is practical: faster builds, near-instant HMR, leaner CSS. With the 6 steps and checklist above you can move your project in one short working session — just watch the plugins.