Tailwind CSS v4 is not an upgrade — it's a rewrite. The team rebuilt the engine in Rust, dropped the JavaScript config file entirely, changed how you import it, renamed classes, and made content detection automatic. If you open a v3 project with v4 installed, you will have errors.
The good news: there's an automated migration tool that handles ~90% of the work, and every breaking change has a direct fix. This guide covers all of them.
Why v4 Is a Big Deal
Three things changed fundamentally:
- The engine — rebuilt in Rust (via Lightning CSS). Cold builds are 5–10x faster. Incremental rebuilds happen in microseconds.
- The config — moved from
tailwind.config.jsto CSS-native@themeblocks inside your stylesheet. - The PostCSS plugin — replaced. The old
tailwindcssPostCSS plugin is gone. You now use@tailwindcss/postcss.
Browser support minimum: Safari 16.4+, Chrome 111+, Firefox 128+. If you need to support anything older, stay on v3.4 for now.
Run the Migration Tool First
Before touching anything manually, run the official upgrade tool:
npx @tailwindcss/upgradeThis handles approximately 90% of mechanical changes automatically — class renames, config migration, import updates. It shows a diff of everything it touched. Review it, then deal with whatever's left manually.
If you're on a large project, run it on a branch:
git checkout -b tailwind-v4-migration
npx @tailwindcss/upgrade
git diffBreaking Change #1 — No More tailwind.config.js
What changed: The JavaScript config file is gone. Design tokens (colors, spacing, fonts, breakpoints) now live in your CSS file using @theme.
Before (v3):
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
brand: '#F97316',
surface: '#0D1117',
},
fontFamily: {
syne: ['Syne', 'sans-serif'],
},
},
},
}After (v4):
/* globals.css */
@import "tailwindcss";
@theme {
--color-brand: #F97316;
--color-surface: #0D1117;
--font-syne: 'Syne', sans-serif;
}Those CSS variables become Tailwind utilities automatically. bg-brand, text-brand, font-syne — all work without any extra config.
What to do: Run the migration tool. It converts your tailwind.config.js to @theme blocks automatically. Review the output and adjust any custom plugins or safelist entries manually.
Breaking Change #2 — @tailwind Directives Are Gone
What changed: The old three-liner at the top of your CSS file no longer works.
Before (v3):
@tailwind base;
@tailwind components;
@tailwind utilities;After (v4):
@import "tailwindcss";One line. That's it. The migration tool replaces this automatically.
If you use a bundler (Vite, Next.js, webpack), also update the PostCSS plugin:
npm install @tailwindcss/postcss// postcss.config.js
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}For Vite specifically, there's a dedicated plugin that's even faster:
npm install @tailwindcss/vite// vite.config.js
import tailwindcss from '@tailwindcss/vite'
export default {
plugins: [tailwindcss()],
}Breaking Change #3 — Content Detection Is Automatic
What changed: You no longer need to specify the content array. Tailwind v4 scans your project automatically.
Before (v3):
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
}After (v4): Nothing. It just works.
Tailwind v4 detects all files in your project root that contain class names. The only exception is if you need to explicitly exclude paths — you can do that with @source in your CSS:
@import "tailwindcss";
@source "../node_modules/some-ui-library";Breaking Change #4 — Class Renames
Several classes were renamed to match their actual CSS property names. The migration tool handles all of these, but you should know what changed:
Gradients
| v3 | v4 |
|---|---|
bg-gradient-to-r | bg-linear-to-r |
bg-gradient-to-l | bg-linear-to-l |
bg-gradient-to-t | bg-linear-to-t |
bg-gradient-to-b | bg-linear-to-b |
bg-gradient-to-tr | bg-linear-to-tr |
bg-gradient-to-tl | bg-linear-to-tl |
bg-gradient-to-br | bg-linear-to-br |
bg-gradient-to-bl | bg-linear-to-bl |
Flex utilities
| v3 | v4 |
|---|---|
flex-shrink-0 | shrink-0 |
flex-shrink | shrink |
flex-grow-0 | grow-0 |
flex-grow | grow |
Other renames
| v3 | v4 |
|---|---|
overflow-ellipsis | text-ellipsis |
decoration-clone | box-decoration-clone |
decoration-slice | box-decoration-slice |
If you have any of these in your codebase, run a global find-and-replace after the migration tool runs.
Breaking Change #5 — Default Border Color Changed
What changed: In v3, border without a color specified defaulted to gray-200. In v4 it defaults to currentColor.
What breaks: Any border or divide utilities you used without specifying a color will now render differently.
How to fix:
Option 1 — add explicit color everywhere it matters:
<!-- v3: border was gray-200 automatically -->
<div class="border">...</div>
<!-- v4: must be explicit -->
<div class="border border-gray-200">...</div>Option 2 — override the default in @theme:
@theme {
--default-border-color: var(--color-gray-200);
}Breaking Change #6 — ring Default Width Changed
What changed: ring in v3 added a 3px ring. In v4 it's 1px.
How to fix:
If you want the v3 behavior:
<!-- v3 -->
<button class="ring">...</button>
<!-- v4 equivalent -->
<button class="ring-3">...</button>New Features Worth Using
Arbitrary variants without plugins
In v4 you can write arbitrary CSS variants directly in HTML without needing a plugin:
<div class="[&>p]:text-sm [&>p]:text-white/60">...</div>@starting-style support
Built-in support for CSS @starting-style for enter/exit animations:
@layer utilities {
.animate-enter {
@starting-style {
opacity: 0;
transform: translateY(-8px);
}
}
}Container queries built-in
Container queries are now part of Tailwind core — no plugin required:
<div class="@container">
<div class="@sm:grid-cols-2 @lg:grid-cols-4">...</div>
</div>Dynamic values without arbitrary syntax
In v3, spacing values not in your scale required w-[37px]. In v4, the scale is continuous — w-37 just works.
Next.js Specific Setup
If you're on Next.js, the setup is slightly different:
npm install tailwindcss@latest @tailwindcss/postcss// postcss.config.mjs
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
}
export default config/* app/globals.css */
@import "tailwindcss";
@theme {
/* your custom tokens here */
}Remove tailwind.config.ts entirely after running the migration tool.
Full Migration Checklist
[ ] git checkout -b tailwind-v4-migration
[ ] npm install tailwindcss@latest @tailwindcss/postcss
[ ] npx @tailwindcss/upgrade (review the diff carefully)
Manual checks after the tool runs:
[ ] Replace @tailwind directives with @import "tailwindcss"
[ ] Update PostCSS config to use @tailwindcss/postcss
[ ] Remove tailwind.config.js (migrated to @theme in CSS)
[ ] Check border utilities — default color changed to currentColor
[ ] Check ring utilities — default width changed to 1px
[ ] Search for bg-gradient-to-* → bg-linear-to-*
[ ] Search for flex-shrink/flex-grow → shrink/grow
[ ] Test in Safari 16.4+, Chrome 111+, Firefox 128+
[ ] Verify build output looks correct
[ ] Merge and deploy
Should You Migrate Now?
Yes, if:
- You're starting a new project — use v4 from day one
- Your team is on modern browsers (Safari 16.4+)
- Build time is a pain point — the 5–10x speedup is real
- You want container queries and other new features
Wait, if:
- You need IE or old Safari support
- You have a large, complex
tailwind.config.jswith many plugins — test thoroughly before committing - You're mid-sprint and can't afford any visual regressions right now
The migration is well worth it long-term. The Rust engine is genuinely much faster, and the CSS-first config model is cleaner than a JavaScript file. Give yourself a quiet afternoon, run the upgrade tool, and fix what's left.
Building a Next.js project from scratch? Check out our Next.js Server vs Client Components guide for the full modern stack setup.