Tailwind CSS v4 — Everything That Changed and How to Migrate
Advertisement
Introduction
Why This Matters
Tailwind CSS v4 is not just an update — it is a rewrite that changes where and how you configure Tailwind. The JavaScript config file is replaced by CSS-based configuration. Build times are dramatically faster. Understanding what changed prevents painful migration surprises.
The New Engine: Oxide
Tailwind v4 ships a new build engine called Oxide, written in Rust. The performance improvement is substantial:
- Full builds: up to 5x faster than v3
- Incremental builds: up to 100x faster (sub-millisecond in many cases)
- No more separate PostCSS plugin step required
CSS-First Configuration
The biggest conceptual change: configuration moves from tailwind.config.js to your CSS file using @theme.
v3 approach (JavaScript config):
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
brand: {
50: '#eff6ff',
500: '#3b82f6',
900: '#1e3a8a'
}
},
fontFamily: {
sans: ['Inter', 'sans-serif']
},
borderRadius: {
xl: '1.5rem'
}
}
}
}v4 approach (CSS config):
/* globals.css */
@import "tailwindcss";
@theme {
--color-brand-50: #eff6ff;
--color-brand-500: #3b82f6;
--color-brand-900: #1e3a8a;
--font-sans: 'Inter', sans-serif;
--radius-xl: 1.5rem;
}These CSS variables are automatically available as Tailwind utility classes: text-brand-500, font-sans, rounded-xl.
Using CSS Variables in Components
Because theme values are real CSS custom properties, they work in your stylesheet too:
.hero-gradient {
background: linear-gradient(
135deg,
var(--color-brand-500),
var(--color-brand-900)
);
}And directly in HTML via arbitrary values:
<div class="bg-[var(--color-brand-500)]">...</div>New Utilities in v4
<!-- Subgrid: inherit parent grid tracks -->
<div class="grid grid-cols-3">
<div class="col-span-3 grid grid-cols-subgrid">
<span>A</span><span>B</span><span>C</span>
</div>
</div>
<!-- Field sizing: input grows with content -->
<textarea class="field-sizing-content resize-none"></textarea>
<!-- Not variant: style elements that don't match a selector -->
<ul>
<li class="not-last:border-b py-3">Item 1</li>
<li class="not-last:border-b py-3">Item 2</li>
<li class="not-last:border-b py-3">Item 3</li>
</ul>
<!-- Starting style: animate elements on initial render -->
<div class="transition-opacity starting:opacity-0 opacity-100">
Fades in on first render
</div>Simplified Import
In v3 you needed three @tailwind directives. In v4 it is a single import:
/* v3 */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* v4 */
@import "tailwindcss";Migration from v3
# Install v4
npm install -D tailwindcss@next @tailwindcss/vite
# Or use the automated upgrade tool
npx @tailwindcss/upgradeKey breaking changes:
| v3 | v4 |
|---|---|
tailwind.config.js | @theme in CSS |
@tailwind base/components/utilities | @import "tailwindcss" |
bg-opacity-50 | bg-black/50 |
ring-opacity-50 | ring-black/50 |
divide-opacity-50 | divide-black/50 |
The automated upgrade tool handles most of these changes. Run it first, then fix any remaining issues manually.
Using v4 with Next.js
npm install -D tailwindcss@next @tailwindcss/postcss// postcss.config.mjs
export default {
plugins: {
'@tailwindcss/postcss': {}
}
}/* app/globals.css */
@import "tailwindcss";
@theme {
--color-primary: #6366f1;
--color-primary-dark: #4f46e5;
--font-sans: 'Inter var', sans-serif;
}Common Mistakes
- Keeping both
tailwind.config.jsand@themeduring migration — they conflict; remove the config file - Using old opacity utilities (
bg-opacity-*,text-opacity-*) — they are removed in v4, use slash syntax instead - Not running
npx @tailwindcss/upgrade— it handles 90% of the mechanical migration work - Expecting PostCSS plugin to work the same — use
@tailwindcss/postcssfor v4
Best Practices
- Start new projects directly with v4 — skip v3 entirely
- Use
@themevariables for all custom colors so they work in both Tailwind classes and arbitrary CSS - Use the
not-*variant for cleaner sibling element styling instead of complex selector hacks
Key Takeaways
- Tailwind v4's Oxide engine provides up to 5x faster full builds and 100x faster incremental builds
- Configuration moves from
tailwind.config.jsto@themeblocks in CSS files — the JS config file is gone - All theme values in
@themebecome real CSS custom properties, usable anywhere in CSS withvar() - The new single import
@import "tailwindcss"replaces the three-line@tailwinddirective setup - Opacity utilities (
bg-opacity-*) are removed — use slash syntax (bg-black/50) instead npx @tailwindcss/upgradeautomates most of the migration from v3- New utilities include subgrid, field-sizing,
not-*variant, andstarting:for initial render animations - Theme values become utility class names automatically —
--color-brand-500generatestext-brand-500,bg-brand-500, etc.
Advertisement