Tailwind v4 Migration: 5 Critical Steps to Upgrade Safely
Tailwind v4 migration breaking pages silently? Master the CSS-first @theme shift in 5 steps and upgrade safely.
20+ years shipping production JavaScript and front-end systems at scale. Notes here come from systems that actually shipped.
- ✓A Tailwind v3 project (or basic Tailwind familiarity)
- ✓Node.js 20+ for the upgrade tool
- ✓A Vite or PostCSS build you can modify
- Tailwind v4 moves configuration from tailwind.config.js into CSS via @theme — tokens become native CSS variables that generate utilities
- Four migration pillars: @import "tailwindcss" entry, @tailwindcss/vite build plugin, @theme tokens, @utility for custom utilities
- Build performance jumps ~10x: large projects compile in sub-100ms vs multi-second v3 builds, with automatic content detection
- Production lesson: an upgrade-tool-only migration shipped 40 broken pages with a green build — renamed utilities fail silently, only visual regression catches them
- Browser baseline is mandatory: Safari 16.4+, Chrome 111+, Firefox 128+ for @property and color-mix() support
- Safety rule: keep tailwind.config.js via @config as a bridge, migrate tokens incrementally, screenshot-diff before shipping
Imagine you used to order custom paint by phoning a warehouse (tailwind.config.js) that mixed colorsjs behind closed doors. Tailwind v4 moves the mixing station into your own studio (@theme in CSS): you mix the colors yourself, you can see every jar on the shelf in DevTools, and any other painter can use your jars just by borrowing the shelf. Same paint, but now the recipe is visible, inspectable, and shareable instead of locked in someone else's warehouse.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Tailwind v4 isn't a patch — it's a rewrite. Configuration moves from JavaScript into CSS, the build moves from PostCSS to Vite, and the engine gets roughly 10x faster.
That much change breaks things. Renamed utilities, dead directives, vanished configs — each one fails quietly, showing up as unstyled elements rather than build errors.
This guide gives you the five-step migration in order, the @theme mental model that replaces tailwind.config.js, and the visual-regression safety net that catches what automation misses.
The Big Shift: CSS-First Configuration With @theme
The core shift is simple: your design tokens move from JavaScript into CSS. Instead of theme.extend.colors in tailwind.config.js, you write @theme { --color-brand-500: oklch(...); } in your stylesheet. Tailwind then generates bg-brand-500, text-brand-500, and every other color utility from that one variable.
The same variable is simultaneously available as var(--color-brand-500) in your custom CSS. One definition feeds both systems — utilities and hand-written styles can never disagree about what 'brand' means.
Because tokens are plain CSS, they're inspectable in DevTools, composable with color-mix(), and shareable across projects as importable CSS files. Config values stopped being build-time secrets and became runtime citizens.
The 5-Step Migration Path in Order
Do it in this order and each step is verifiable. First, update dependencies: remove tailwindcss v3/postcss/autoprefixer where replaced, add tailwindcss v4 plus @tailwindcss/vite (or the CLI package). Node 20+ is required for the tooling.
Second, run the official upgrade tool — it rewrites dependencies, converts basic config to CSS, and fixes common template patterns. Review its diff like a human PR; it covers ~80%, not 100%.
Third, replace the entry CSS: @tailwind base/components/utilities becomes @import "tailwindcss" (first import in the file). Fourth, migrate remaining theme values to @theme and rewrite custom utilities with @utility. Fifth, screenshot-diff everything before shipping.
Build Pipeline Changes: Vite Plugin and Lightning CSS
The build plumbing changes alongside the config. In v4, Tailwind runs as a Vite plugin (@tailwindcss/vite) instead of a PostCSS plugin — your dependency bump is also a pipeline change. Update vite.config.ts to register the plugin and drop postcss.config.js if Tailwind was its only client.
Autoprefixer and postcss-import are generally unnecessary now: v4 handles vendor prefixing internally via Lightning CSS and bundles imports itself. Fewer config files, fewer version conflicts.
The payoff is speed. The rewritten engine parses sources and generates CSS on demand so efficiently that most projects compile in sub-10ms and even huge ones stay under 100ms. CI style steps effectively disappear from timing charts.
API Swaps: @utility, var(), and Dead Configs
Three API swaps bite almost everyone. @apply with !important patterns should move to @utility, which participates in variants and responsive prefixes properly. The theme() function in CSS is gone — use native var(--token) since every @theme value is a real CSS variable. And safelist config is largely unnecessary because v4's content detection is far more thorough.
Container queries deserve a look while you're here: v4 supports @container natively, so responsive-to-parent patterns that needed plugins in v3 now work out of the box.
Handle each swap with a project-wide search (theme(, @apply, safelist) and fix every hit before the regression pass. Stragglers hide in rarely-visited stylesheets.
theme() call in an admin stylesheet silently produced invalid CSS that blanked the entire admin sidebar. It lived in a file nobody screenshotted. Project-wide search would have found it in seconds. Rule: grep the three patterns before every v4 deploy, no exceptions.Browser Baseline and the OKLCH Palette Shift
This step is non-negotiable: v4 requires Safari 16.4+, Chrome 111+, and Firefox 128+. The framework depends on @property, color-mix(), and cascade layers at its core — older browsers don't just degrade, they break.
Check your analytics and support matrix before migrating. Enterprise and emerging-market audiences often still include older browsers; for them, staying on v3.4 is the correct professional call, not a failure.
Also note the default palette now uses OKLCH for perceptually uniform color. Slight shade shifts versus v3 are expected — that's another reason the visual regression pass matters. Some 'breakage' is just better color science.
Finishing Clean: Proof, Regression, and Handoff
Close it out cleanly. Keep @config pointing at tailwind.config.js until a build without it produces byte-identical CSS — that's your proof the token migration is complete. Then delete the JS file and celebrate with a smaller repo.
Run the full build with a cold cache, screenshot-diff all key pages at mobile, tablet, and desktop, and exercise interactive states (hover, focus, dark mode) that static screenshots miss.
Document the new token home for the team: @theme blocks in entry CSS, shared brand tokens as importable CSS files. Future theming work now happens in stylesheets, not JavaScript.
The Green Build That Shipped 40 Broken Pages
- A green build means nothing in CSS migrations — only visual regression across real pages proves the styles survived.
- Automated codemods handle ~80% of renames; budget explicit human time for the long tail of skipped utilities.
- Keep the old config as a bridge (@config) and migrate incrementally — deleting it first turns one migration into an all-at-once restyle.
| File | Command / Code | Purpose |
|---|---|---|
| entry.css | /* BEFORE (v3): entry.css */ | The 5-Step Migration Path in Order |
| utilities.css | /* v3 patterns */ | API Swaps |
Key takeaways
Common mistakes to avoid
4 patternsLeaving custom @imports above the Tailwind import
layer(). CSS requires @import first — Tailwind v4 just enforces what the spec always said.Assuming every v3 utility class still exists
Defining @theme tokens that collide with defaults
Deleting tailwind.config.js before migrating its contents
Interview Questions on This Topic
What are the headline breaking changes in Tailwind CSS v4?
theme() function with native CSS variables. It's also dramatically faster (sub-100ms builds) and requires modern browsers (Safari 16.4+, Chrome 111+).Frequently Asked Questions
20+ years shipping production JavaScript and front-end systems at scale. Notes here come from systems that actually shipped.
That's CSS. Mark it forged?
3 min read · try the examples if you haven't