Tailwind CSS v4 is the most significant release since v1. The engine was rewritten in Rust (via the oxide engine), the JavaScript config file is replaced by CSS-native configuration, and the full build pipeline is redesigned for near-instant HMR. If you've been using Tailwind v3, here's what changed and exactly how to migrate.
The Big Changes at a Glance
- Rust engine: 5–10× faster full builds, sub-millisecond HMR in most projects
- CSS-first configuration: no more
tailwind.config.js— customize via CSS@themedirective - No PostCSS config required: single
@import "tailwindcss"in your CSS is enough - Automatic content detection: no
contentarray to maintain - New utilities:
field-sizing,starting-style,not-*variant,inert, masking utilities - CSS variables for everything: every design token is a CSS custom property
- Composable variants:
group-hover/sidebar,peer-focus/inputnamed groups and peers
Installation
# With Vite (recommended)
npm install tailwindcss @tailwindcss/vite
# With PostCSS (for non-Vite projects)
npm install tailwindcss @tailwindcss/postcss
Vite config:
// vite.config.js
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})
Then in your main CSS file, just one line:
@import "tailwindcss";
That's it. No @tailwind base, no @tailwind components, no @tailwind utilities. No tailwind.config.js.
CSS-First Configuration with @theme
In v3, you customized Tailwind in JavaScript:
// tailwind.config.js (v3)
module.exports = {
theme: {
extend: {
colors: {
brand: {
500: '#2563eb',
600: '#1d4ed8',
},
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
},
},
}
In v4, you do this in CSS:
@import "tailwindcss";
@theme {
--color-brand-500: #2563eb;
--color-brand-600: #1d4ed8;
--font-sans: 'Inter', sans-serif;
--spacing-18: 4.5rem;
--radius-xl: 1rem;
}
These @theme variables become CSS custom properties at :root, so you can also use them directly in non-Tailwind CSS:
.hero {
background-color: var(--color-brand-500);
border-radius: var(--radius-xl);
}
And the Tailwind utility classes are generated automatically: bg-brand-500, text-brand-600, rounded-xl.
Automatic Content Detection
In v3, you had to specify where Tailwind should look for class names:
// tailwind.config.js (v3)
content: ['./src/**/*.{html,js,ts,jsx,tsx}'],
In v4, Tailwind automatically scans your project using the same logic as Vite — it reads your project's file tree and scans all files that could contain class names. You almost never need to override this. If you have edge cases (e.g., class names stored in a database or generated at runtime), use @source:
@import "tailwindcss";
@source "./src/templates/**/*.html"; /* explicit additional source */
@source inline("bg-red-500 text-lg"); /* inline safelisting */
New Utilities Worth Knowing
field-sizing — Auto-Resizing Textareas
<textarea class="field-sizing-content w-full"></textarea>
The field-sizing: content CSS property makes textareas grow with their content automatically, with no JavaScript required. Tailwind v4 exposes this as field-sizing-content and field-sizing-fixed.
not-* Variant
<!-- Style all children that are NOT the last -->
<li class="not-last:border-b border-gray-200">Item</li>
<!-- Style when NOT hovered -->
<button class="not-hover:opacity-70">Click me</button>
The not-* variant maps to the :not() CSS pseudo-class. You can negate any selector variant: not-focus:ring-0, not-disabled:cursor-pointer, etc.
starting-style — Entry Animations Without JavaScript
<dialog class="
opacity-100 transition-all duration-300
starting:opacity-0 starting:translate-y-2
">...</dialog>
The @starting-style CSS rule (now exposed as the starting: variant) defines the styles from which an element transitions when it first appears. This enables enter animations for dialogs, popovers, and elements entering the DOM without JavaScript.
Named Groups and Peers
<nav class="group/sidebar">
<a href="#" class="group-hover/sidebar:text-blue-500">Home</a>
</nav>
<form>
<input type="email" class="peer/email" />
<p class="hidden peer-invalid/email:block text-red-500">
Please enter a valid email.
</p>
</form>
Named groups (group/name) and named peers (peer/name) let you scope group/peer interactions when you have nested or sibling groups — eliminating the need for workarounds that were common in v3.
Migrating from Tailwind v3
Step 1: Update packages
npm uninstall tailwindcss postcss autoprefixer
npm install tailwindcss @tailwindcss/vite # or @tailwindcss/postcss for non-Vite
# Remove old config files
rm tailwind.config.js postcss.config.js
Step 2: Update your CSS entry file
/* Before (v3) */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* After (v4) */
@import "tailwindcss";
Step 3: Move theme customizations to @theme
/* Before: tailwind.config.js theme.extend.colors */
/* After: in your CSS file */
@theme {
--color-primary: #2563eb;
--color-primary-dark: #1d4ed8;
}
Step 4: Update renamed utilities
Some utilities changed names in v4. The official upgrade tool handles most automatically:
npx @tailwindcss/upgrade
Notable renames: shadow-sm → shadow-xs, ring (default 3px) → ring-3, blur → blur-sm, inset-* now also covers top/right/bottom/left independently.
Step 5: Handle custom plugins
The plugin API is unchanged but plugins are now registered in CSS:
@import "tailwindcss";
@plugin "@tailwindcss/forms";
@plugin "@tailwindcss/typography";
Performance Comparison
On a large Next.js project (500+ components, extensive custom theme):
- Full build v3: ~2.3 seconds → v4: ~0.28 seconds (8× faster)
- HMR incremental build v3: ~140ms → v4: ~12ms
- Cold install with Vite: noticeably snappier feedback loop
The difference is most pronounced on larger codebases. Small projects see a 3–4× improvement; large monorepos with shared design systems see the biggest gains.
Summary
Tailwind CSS v4 is a major quality-of-life upgrade: faster builds, simpler setup, and CSS-native customization that integrates naturally with design systems and CSS custom properties. The migration is largely automated by the upgrade tool. For new projects, there's no reason to start with v3. For existing v3 projects, the migration is low-risk and the build speed improvement pays for itself quickly.
CREESOL