TailwindCSS v4 Migration Guide: What Changed and How to Upgrade
A practical guide to migrating from TailwindCSS v3 to v4. Covers the new CSS-first configuration, updated color system, removed utilities, and step-by-step upgrade path.

I migrated this portfolio from TailwindCSS v3 to v4, and the upgrade was smoother than expected — but there are breaking changes you need to know about. Here’s what I learned.
TAILWIND V4 IN ONE GLANCE
The upgrade is not just a version bump. It changes where configuration lives, how the tool integrates with Vite, and which utility assumptions still hold.
CONFIG
The center of gravity moves into CSS
Tokens now live in `@theme`, which makes them visible as CSS variables and easier to inspect or reuse.
- No more JS-only theme config
- Native CSS variables as design tokens
- Better DevTools visibility
TOOLING
Tailwind plugs into Vite directly
The v4 setup is simpler, but it means your migration is partly a build-pipeline migration too.
- Swap PostCSS-centric setup for the Vite plugin
- Review config files you can delete
- Confirm your entry CSS is updated
BREAKING EDGES
Several utility assumptions changed
Opacity syntax, shadow naming, border defaults, and CSS helper behavior can break quietly if you do not review them explicitly.
- Check renamed utilities
- Audit implicit border colors
- Replace old `theme()` usage
PAYOFF
The upgrade earns its keep on performance
Faster builds, cleaner config, and easier container-query usage make v4 worth it once you absorb the mental model shift.
- Faster full and incremental builds
- Smaller CSS output
- Container queries become straightforward
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 Migration
MIGRATION FLOW
Do the upgrade in clear passes. Most quiet breakage happens when teams jump from dependency bump straight to visual QA.
SETUP
Upgrade the package and move to the Vite plugin
Tailwind v4 changes how the tool integrates, so the dependency update is also a build-pipeline update.
- Remove the old PostCSS-only setup
- Install `tailwindcss` and `@tailwindcss/vite`
- Confirm Vite is the source of truth
Why this step existsYou start from the supported runtime model instead of layering v4 on top of a v3 pipeline.
CLEANUP
Delete config that no longer drives the build
Old config files can create false confidence if they remain in the repo after the upgrade.
- Remove stale Tailwind config files
- Drop PostCSS config if Tailwind was the only reason it existed
- Simplify the mental model before class-level fixes
Why this step existsYou reduce the chance that teammates think legacy config is still active.
TOKENS
Move theme values into CSS
The main mental shift in v4 is that tokens live in `@theme` and become native CSS variables.
- Port fonts, colors, breakpoints, and tokens
- Check that token names still map to your utilities
- Use DevTools to inspect them directly
Why this step existsYou align the design-token layer with how v4 wants to be configured.
AUDIT
Review renamed utilities and changed defaults
The riskiest regressions come from small assumptions: borders, shadows, blur, opacity, and old helper functions.
- Add explicit border colors
- Update slash-opacity syntax
- Replace old `theme()` usage
Why this step existsYou catch the class-level changes that make layouts look subtly wrong.
VERIFY
Run the codemod, then verify by hand
The upgrader is useful, but it is not a replacement for reading the diff and smoke-testing real pages.
- Run the official upgrader
- Review the diff manually
- Test high-traffic pages after the migration
Why this step existsYou finish with a cleaner migration instead of a partially automated one.
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.tspostcss.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 utilitiestheme()function in CSS: Replaced by native CSS custom properties (var(--color-brand-accent))safelistconfig: Not needed — v4’s detection is more thoroughdarkModeconfig: 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> SAFE MIGRATION PATH VS QUIET FAILURE POINTS
Most teams can upgrade without drama if they do it in deliberate passes. The risky part is assuming the codemod or dependency bump caught everything important.
SAFE PATH
Migrate in controlled passes
- Upgrade dependencies and wire the Vite plugin first
- Port design tokens into `@theme` before chasing class-level diffs
- Run the official upgrader, then review the diff manually
- Smoke-test key pages after updating border, shadow, and blur utilities
QUIET BREAKAGE
These are the places teams usually miss
- Implicit border colors now inheriting from `currentColor`
- Old opacity utilities that need the slash syntax
- `theme()` usage left behind in CSS files
- Assuming JS config and old plugins are still being read
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.
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
Written by Umesh Malik
AI Engineer & Software Developer. Building GenAI applications, LLM-powered products, and scalable systems.
Related Articles

Career
Frontend Career Growth: From Junior to Senior Engineer
Lessons from my journey from Associate Engineer to SDE-2. What actually matters for career growth in frontend engineering — technical skills, soft skills, and the things nobody tells you.

Performance
Core Web Vitals Optimization: A Practical Guide
A hands-on guide to optimizing Core Web Vitals (LCP, INP, CLS). Covers measurement, diagnosis, and specific fixes with before/after examples from real projects.

Testing
Frontend Testing Strategies That Actually Work in 2025
A pragmatic guide to frontend testing in 2025. Covers component testing, integration tests, E2E strategies, and the testing patterns that deliver the most confidence per line of test code.