TailwindCSS v4 migration is mostly painless — I moved this portfolio from v3 to v4 in an afternoon — but a handful of breaking changes bite quietly if you don't know them. Here's the CSS-first @theme shift, every breaking change worth watching, and a step-by-step upgrade path. If you are modernizing a frontend stack, it pairs well with the ES2024 features worth adopting, my Core Web Vitals optimization guide, and how Cloudflare, Vite, and Next.js are reshaping that same build pipeline. See also Frontend Testing Strategies That Actually Work in 2026.
TL;DR
-
TailwindCSS v4 migration moves your config out of
tailwind.config.jsand into CSS via the new@themeblock — design tokens become native CSS variables. - Tailwind is now a Vite plugin, not PostCSS — so the dependency bump is also a build-pipeline change.
- The quiet breakers:
bordernow defaults tocurrentColor, opacity uses slash syntax (bg-black/50), and the shadow/blur utilities were renamed. - Run
npx @tailwindcss/upgrade, then review the diff by hand — the codemod caught ~90% of changes in my case. - The payoff: up to 10× faster builds and, on this site, a 32% smaller CSS bundle.
The Big Shift: CSS-First Configuration
The biggest change in Tailwind v4 is that configuration moves from tailwind.config.js into your CSS file using @theme.
Before (v3)
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
brand: {
accent: '#C09E5A',
black: '#000000',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
},
},
plugins: [require('@tailwindcss/typography')],
};
After (v4)
/* app.css */
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
@theme {
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--color-brand-accent: #C09E5A;
--color-brand-black: #000000;
}
This is a significant philosophical change. Your design tokens are now CSS custom properties, which means:
- They're inspectable in browser DevTools
- They work with native CSS features like
color-mix() - No build step needed to read your config values
Step-by-Step TailwindCSS v4 Migration
1. Update Dependencies
pnpm remove tailwindcss postcss autoprefixer
pnpm add tailwindcss@latest @tailwindcss/vite
In v4, Tailwind runs as a Vite plugin instead of PostCSS. Update your vite.config.ts:
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
});
2. Remove Old Config Files
Delete these if they exist:
-
tailwind.config.js/tailwind.config.ts -
postcss.config.js(if only used for Tailwind)
3. Update Your CSS Entry Point
Replace the old directives:
/* Before */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* After */
@import 'tailwindcss';
4. Migrate Theme Config to @theme
Move your tailwind.config.js theme values into @theme blocks in your CSS:
@theme {
--font-display: 'Inter', system-ui, sans-serif;
--color-brand-accent: #C09E5A;
--color-brand-border: #2B2B2B;
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
}
5. Update Plugin Usage
/* Before: require() in config */
/* After: @plugin directive in CSS */
@plugin '@tailwindcss/typography';
Breaking Changes to Watch For
Renamed Utilities
Several utility classes were renamed for consistency:
| v3 | v4 |
|---|---|
bg-opacity-50 |
bg-black/50 (opacity modifier) |
text-opacity-75 |
text-white/75 |
shadow-sm |
shadow-xs |
shadow |
shadow-sm |
ring |
ring-3 |
blur |
blur-sm |
Removed Features
-
@applywith!important: Use@utilityinstead for custom utilities -
theme()function in CSS: Replaced by native CSS custom properties (var(--color-brand-accent)) -
safelistconfig: Not needed — v4's detection is more thorough -
darkModeconfig: Always uses@media (prefers-color-scheme: dark)or class strategy via CSS
Default Border Color Changed
In v3, border defaulted to gray-200. In v4, it defaults to currentColor. Add explicit colors:
<!-- Before (v3) -->
<div class="border">...</div>
<!-- After (v4) — add explicit color -->
<div class="border border-gray-200">...</div>
Container Queries
Tailwind v4 has first-class container query support:
<div class="@container">
<div class="@sm:flex @md:grid @md:grid-cols-2">
<!-- Responds to container size, not viewport -->
</div>
</div>
New Color System
The default color palette uses OKLCH color space, which provides more perceptually uniform colors. If you're using custom colors, they'll still work fine.
Automated Migration
Tailwind provides a codemod to automate most of the migration:
npx @tailwindcss/upgrade
This handles renaming utilities, updating imports, and converting your config. I'd still recommend reviewing the diff manually — the codemod caught about 90% of changes in my case.
Performance Improvements
v4 is significantly faster:
- Build times: Up to 10x faster full builds
- Incremental builds: Up to 100x faster during development
- Bundle size: Smaller CSS output thanks to better dead-code elimination
In this portfolio, the CSS bundle dropped from 28KB to 19KB after migration — a 32% reduction with zero visual changes.
FAQ
Key Takeaways
- The CSS-first approach is the biggest mental shift — embrace it
- Use the automated migration tool, but review the output
- Update border utilities to include explicit colors
- Shadow and blur class names have shifted — check your components
- The performance improvements alone make the upgrade worthwhile
- Container queries are now trivial to use
Sources
- Tailwind CSS v4.0 — official announcement
- Tailwind CSS — Upgrade guide (v3 → v4)
- Tailwind CSS — Theme configuration (
@theme)
Originally published at umesh-malik.com
Keep reading on umesh-malik.com:
Top comments (0)