# Tailwind CSS 4 Migration: What Changed & How to Upgrade

> How to migrate to Tailwind CSS 4 — the CSS-first config, Oxide engine speed, the breaking changes, and a step-by-step upgrade from v3 for Next.js and Vite.

Source: https://designrevision.com/blog/tailwind-4-migration

---

Tailwind CSS 4.0 is the biggest update in the framework's history. It replaces the JavaScript-based build system with a Rust-powered engine, moves configuration from JavaScript files to native CSS, and delivers build times up to 5x faster than v3.

If you have been writing Tailwind for years, the upgrade changes how you think about configuration. If you are starting fresh, tailwind 4 is simply a better starting point. This guide covers every major change, the complete migration path from v3, and the framework-specific setup for Next.js and Vite projects.
## Key Takeaways

> If you remember nothing else:
>
> * **Tailwind 4** replaces `tailwind.config.js` with CSS-first configuration using the `@theme` directive and CSS custom properties
> * The new Oxide engine (built in Rust) delivers 2-5x faster builds: large projects drop from 600ms to ~120ms
> * The entry point changes from `@tailwind base/components/utilities` to `@import "tailwindcss"`
> * Content detection is automatic in tailwind 4. No more manual `content: [...]` arrays in config
> * New features include native container queries, 3D transforms, `color-mix()` support, and CSS cascade layers
> * The official upgrade tool (`npx @tailwindcss/upgrade`) automates most migration steps
> * PostCSS setup changes: replace `tailwindcss` plugin with `@tailwindcss/postcss`, or use the new `@tailwindcss/vite` plugin

## Table of Contents

1. [Why Tailwind 4 Is a Major Rewrite](#why-tailwind-4-is-a-major-rewrite)
2. [The Oxide Engine: Performance Gains](#the-oxide-engine-performance-gains)
3. [CSS-First Configuration: Goodbye tailwind.config.js](#css-first-configuration-goodbye-tailwindconfigjs)
4. [New Features in Tailwind 4](#new-features-in-tailwind-4)
5. [Breaking Changes: What Moved, Changed, or Disappeared](#breaking-changes-what-moved-changed-or-disappeared)
6. [Step-by-Step Migration Guide](#step-by-step-migration-guide)
7. [Framework-Specific Setup](#framework-specific-setup)
8. [Plugin Compatibility](#plugin-compatibility)
9. [Before and After: Code Comparisons](#before-and-after-code-comparisons)
10. [Common Migration Errors and Fixes](#common-migration-errors-and-fixes)
11. [Should You Upgrade Now?](#should-you-upgrade-now)
12. [Conclusion](#conclusion)

## Why Tailwind 4 Is a Major Rewrite

Tailwind CSS v3 was built on a JavaScript engine that processed your configuration file, scanned your templates, and generated CSS. It worked, but the architecture had limits. Build times scaled poorly on large projects. Configuration lived in JavaScript, separate from the CSS it produced. And the content scanning system required manual file path configuration that broke in monorepos.

Tailwind 4 rewrites the core from scratch. The JavaScript engine is replaced by Oxide, a Rust-based compiler that processes CSS natively. Configuration moves from `tailwind.config.js` into your CSS files using standard CSS syntax. Content detection becomes automatic, scanning your project without manual path configuration.

The result is a framework that is faster, simpler to configure, and more aligned with how CSS actually works. Tailwind css 4 is not a minor version bump with new utilities. It is a foundational change in how the tool operates.

## The Oxide Engine: Performance Gains

The headline improvement in tailwind 4 is raw speed. The Oxide engine replaces the JavaScript-based build pipeline with a Rust compiler that processes CSS directly.

### Build Time Benchmarks

| Metric | Tailwind v3 | Tailwind v4 | Improvement |
|--------|------------|------------|-------------|
| **Full build (large project)** | ~600ms | ~120ms | ~5x faster |
| **Incremental rebuild** | ~100ms | ~15ms | ~6x faster |
| **HMR update** | Noticeable delay | Near-instant | Significant |
| **Cold start** | Moderate | Fast | ~3x faster |

For small projects with a few hundred utility classes, the difference is marginal. For production applications with thousands of classes across hundreds of files, the improvement is transformative. HMR updates that previously caused a visible flash now feel instant.

### Why Rust Matters

The performance gain comes from three architectural changes:

1. **Native CSS parsing.** Oxide reads and generates CSS without converting to and from JavaScript ASTs. This eliminates the serialization overhead that slowed v3
2. **Parallel processing.** Rust enables multi-threaded scanning of source files. Large codebases benefit from parallel content detection
3. **Zero-runtime JavaScript.** The build process produces pure CSS without any runtime JavaScript. No more PostCSS plugin chain for Tailwind processing

For teams using v4 alongside other build tools, the reduced build footprint means faster CI/CD pipelines and quicker development server startup.

## CSS-First Configuration: Goodbye tailwind.config.js

The most significant workflow change in tailwind v4 is the move from JavaScript configuration to CSS-first configuration. Instead of defining your design system in `tailwind.config.js`, you define it directly in your CSS using the `@theme` directive.

### The @theme Directive

The `@theme` directive replaces the `theme` and `extend` sections of your JavaScript config. Every value you define becomes a CSS custom property, making your design tokens available throughout your stylesheets.

**Tailwind v3 configuration:**

```js
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#3B82F6',
        secondary: '#10B981',
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif'],
        mono: ['JetBrains Mono', 'monospace'],
      },
      spacing: {
        '18': '4.5rem',
        '112': '28rem',
      },
    },
  },
}
```

**Tailwind 4 configuration:**

```css
@import "tailwindcss";

@theme {
  --color-primary: #3B82F6;
  --color-secondary: #10B981;
  --font-family-sans: "Inter", sans-serif;
  --font-family-mono: "JetBrains Mono", monospace;
  --spacing-18: 4.5rem;
  --spacing-112: 28rem;
}
```

The CSS approach has concrete advantages beyond simplicity. Your design tokens are native CSS custom properties, accessible from any stylesheet without Tailwind-specific tooling. IDE autocompletion works out of the box. And there is no JavaScript build step between your configuration and your CSS output.

### The @import Entry Point

The entry point for tailwind css 4 changes from three directives to a single import:

**v3:**
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

**v4:**
```css
@import "tailwindcss";
```

This single import handles base styles, component layer, and utilities. The framework uses native CSS cascade layers internally to maintain the correct specificity order.

### Automatic Content Detection

One of the most frustrating aspects of Tailwind v3 was the `content` array. You had to manually specify every file path that contained Tailwind classes. Miss a path and your classes got purged. Monorepos required complex glob patterns.

Version 4 eliminates the content array entirely. The Oxide engine automatically detects which files contain Tailwind classes by scanning your project. No configuration needed. No broken builds because you forgot to add a new directory.

If you need to explicitly include or exclude paths, you can use the `@source` directive:

```css
@import "tailwindcss";
@source "../shared-components/**/*.tsx";
```

But for most projects, automatic detection handles everything. If you have been using our Tailwind cheat sheet as a reference, the utility class names remain the same. The change is only in how the build tool finds them.

## New Features in Tailwind 4

Beyond the architectural rewrite, tailwind 4.0 introduces utilities and capabilities that were previously impossible or required plugins.

### Container Queries

Native container query support lets you style elements based on their parent container's size rather than the viewport. This has been the most requested feature for responsive component design.

```html
<div class="@container">
  <div class="grid @sm:grid-cols-1 @lg:grid-cols-2 @xl:grid-cols-3">
    <div class="p-4">Responsive to container, not viewport</div>
  </div>
</div>
```

Container queries make components truly portable. A card component adapts its layout based on where it is placed, whether that is a narrow sidebar or a wide main content area.

### 3D Transforms

New transform utilities support 3D space without custom CSS:

```html
<div class="perspective-[1000px]">
  <div class="rotate-x-12 rotate-y-6 translate-z-8 transform-3d">
    3D transformed element
  </div>
</div>
```

Utilities include `rotate-x-*`, `rotate-y-*`, `translate-z-*`, `perspective-*`, and `transform-style-3d` for building card flips, parallax effects, and interactive 3D interfaces.

### color-mix() Support

The `color-mix()` function enables dynamic color blending directly in utility classes:

```html
<div class="bg-[color-mix(in_srgb,var(--color-primary)_70%,white)]">
  70% primary, 30% white
</div>
```

This is particularly useful for generating hover states, disabled states, and color variations without defining every shade manually.

### CSS Cascade Layers

The framework uses native CSS `@layer` for organizing styles in v4. The framework creates three layers: `base`, `components`, and `utilities`. Your custom styles integrate into this system naturally:

```css
@import "tailwindcss";

@layer components {
  .btn {
    @apply px-6 py-3 rounded-lg font-medium;
  }
  .card {
    @apply bg-white rounded-xl shadow-sm border border-gray-200;
  }
}
```

Cascade layers guarantee that utilities always override component styles regardless of source order, eliminating a class of specificity bugs that plagued v3 projects.

### Native CSS Variables Everywhere

Every theme value in v4 is a CSS custom property. This means you can access your design tokens from any CSS context, not just through utility classes:

```css
.custom-element {
  background: var(--color-primary);
  font-family: var(--font-family-sans);
  padding: var(--spacing-4);
}
```

This bridges the gap between utility-first and traditional CSS approaches. You get the design system consistency of Tailwind with the flexibility of custom CSS when you need it.

### Improved Gradient APIs

Gradient utilities are more flexible with support for gradient angles, multiple color stops with positions, and interpolation methods:

```html
<div class="bg-linear-to-r from-blue-500 from-20% via-purple-500 via-60% to-pink-500">
  Gradient with explicit stop positions
</div>
```

## Breaking Changes: What Moved, Changed, or Disappeared

Every major version introduces breaking changes. This release changes more than most because of the architectural shift. Here is what you need to know before migrating.

### Configuration Changes

| What Changed | v3 | v4 |
|-------------|----|----|
| **Config file** | `tailwind.config.js` (required) | `@theme` in CSS (recommended) |
| **Entry point** | `@tailwind base/components/utilities` | `@import "tailwindcss"` |
| **Content paths** | `content: ['./src/**/*.tsx']` | Automatic detection |
| **PostCSS plugin** | `tailwindcss` | `@tailwindcss/postcss` |
| **Vite integration** | PostCSS plugin | `@tailwindcss/vite` plugin |
| **CLI** | `npx tailwindcss` | `npx @tailwindcss/cli` |

### Renamed and Updated Utilities

| v3 Utility | v4 Equivalent | Notes |
|-----------|--------------|-------|
| `bg-gradient-to-r` | `bg-linear-to-r` | Aligns with CSS `linear-gradient` naming |
| `bg-opacity-*` | `bg-{color}/{opacity}` | Uses slash syntax: `bg-blue-500/75` |
| `text-opacity-*` | `text-{color}/{opacity}` | Same pattern as background |
| `decoration-slice` | `box-decoration-slice` | Full CSS property name |
| `decoration-clone` | `box-decoration-clone` | Full CSS property name |
| `flex-shrink-*` | `shrink-*` | Simplified (already in late v3) |
| `flex-grow-*` | `grow-*` | Simplified (already in late v3) |
| `overflow-ellipsis` | `text-ellipsis` | Renamed for clarity |

### Default Changes

- **Default border color** changed from `gray-200` to `currentColor`, matching the CSS specification default
- **Default ring width** changed from `3px` to `1px`
- **Color scale** uses updated values. If your design depends on exact hex values from v3 defaults, verify them after migration
- **Dark mode** defaults to `media` strategy. Switch to `class` strategy via CSS if you use manual dark mode toggling

### What Was Removed

- The standalone `tailwindcss` PostCSS plugin. Use `@tailwindcss/postcss` instead
- The `tailwindcss init` command. Configuration lives in CSS now
- The `content` configuration array. Replaced by automatic detection and `@source`
- The `safelist` configuration. Use `@source` for explicit inclusion
- JavaScript-based plugin registration via `plugins: [require('...')]`. Plugins use CSS imports or the `@plugin` directive

## Step-by-Step Migration Guide

Here is the exact process to migrate to tailwind 4 from v3. The official upgrade tool handles most of this automatically, but understanding each step helps when you need to debug issues.

### Step 1: Run the Upgrade Tool

The fastest path is the automated upgrade:

```bash
npx @tailwindcss/upgrade@latest
```

This tool scans your project and automatically:
- Converts `@tailwind` directives to `@import "tailwindcss"`
- Migrates `tailwind.config.js` to `@theme` in your CSS
- Updates PostCSS configuration
- Renames deprecated utilities in your templates
- Adjusts import paths

For most projects, the upgrade tool handles 80% of the migration. Run it first, then fix remaining issues manually.

### Step 2: Update Dependencies

```bash
# Remove old packages
npm uninstall tailwindcss postcss-import

# Install Tailwind 4
npm install tailwindcss@latest

# Install the integration for your build tool
npm install -D @tailwindcss/postcss    # For PostCSS users
# OR
npm install -D @tailwindcss/vite       # For Vite users
# OR
npm install -D @tailwindcss/cli        # For CLI-only builds
```

### Step 3: Update Your CSS Entry Point

Replace the old directives with the new import:

```css
/* Before (v3) */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* After (v4) */
@import "tailwindcss";
```

Add your theme configuration below the import:

```css
@import "tailwindcss";

@theme {
  --color-primary: #3B82F6;
  --color-secondary: #10B981;
  --font-family-sans: "Inter", sans-serif;
}
```

### Step 4: Update Build Configuration

**For PostCSS users**, update `postcss.config.js`:

```js
// Before (v3)
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

// After (v4)
module.exports = {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}
```

Note: autoprefixer is no longer needed. Tailwind css 4 handles vendor prefixing automatically.

**For Vite users**, update `vite.config.js`:

```js
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [tailwindcss()],
})
```

No PostCSS configuration is needed when using the Vite plugin.

### Step 5: Migrate Configuration

Convert your `tailwind.config.js` theme values to `@theme`:

```css
@import "tailwindcss";

@theme {
  /* Colors */
  --color-brand: #6366F1;
  --color-brand-light: #818CF8;
  --color-brand-dark: #4F46E5;

  /* Typography */
  --font-family-heading: "Cal Sans", sans-serif;
  --font-family-body: "Inter", sans-serif;

  /* Custom spacing */
  --spacing-128: 32rem;
  --spacing-144: 36rem;

  /* Border radius */
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;

  /* Breakpoints */
  --breakpoint-3xl: 1920px;
}
```

If you still need JavaScript configuration (for complex plugins or dynamic values), use the `@config` directive:

```css
@import "tailwindcss";
@config "../tailwind.config.js";
```

This backward compatibility bridge lets you migrate incrementally.

### Step 6: Delete Old Configuration Files

Once everything works with CSS-first configuration:

```bash
rm tailwind.config.js
```

Also remove any `postcss-import` references and old Tailwind-specific PostCSS plugins from your project.

### Step 7: Test and Fix

Run your build and check for:
- Missing styles (classes that got purged incorrectly)
- Color differences (default palette values changed)
- Dark mode behavior (verify your strategy is applied correctly)
- Plugin output (ensure third-party plugins render correctly)

The migration is complete when your build passes and your UI matches its pre-migration appearance. For projects using [Tailwind templates](/blog/best-tailwind-templates), verify that template-specific customizations survive the migration.

## Framework-Specific Setup

### Next.js Setup

For [Next.js projects with Tailwind](/blog/tailwind-nextjs-setup), the setup depends on whether you use the Pages Router or App Router.

**App Router (recommended):**

Install the PostCSS plugin:

```bash
npm install -D @tailwindcss/postcss
```

Update `postcss.config.mjs`:

```js
const config = {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};
export default config;
```

Update your global CSS file (`app/globals.css`):

```css
@import "tailwindcss";

@theme {
  --color-primary: #3B82F6;
  --font-family-sans: "Inter", sans-serif;
}
```

Import it in `app/layout.tsx`:

```tsx
import './globals.css'
```

No `tailwind.config.js` or `tailwind.config.ts` needed. Delete them after migration.

### Vite + React Setup

```bash
npm install -D @tailwindcss/vite
```

Update `vite.config.ts`:

```ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
})
```

Update `src/index.css`:

```css
@import "tailwindcss";
```

Import in your root component. No PostCSS configuration needed.

### CLI-Only Setup

For projects without a bundler:

```bash
npm install -D @tailwindcss/cli
```

Build your CSS:

```bash
npx @tailwindcss/cli -i src/input.css -o dist/output.css --watch
```

The input CSS file uses the same `@import "tailwindcss"` syntax.

## Plugin Compatibility

Plugin compatibility is the primary concern for teams considering the migrate to tailwind 4 process. Here is the status of major plugins.

### Fully Compatible

| Plugin | Status | Notes |
|--------|--------|-------|
| **Headless UI** | Works | No changes needed. Headless components use className props |
| **Radix UI** | Works | Headless library, applies Tailwind classes normally |
| **shadcn/ui** | Works | Updated for v4 CSS variables approach |
| **Tailwind Typography** | Works | Updated for v4 as `@tailwindcss/typography` |
| **Tailwind Forms** | Works | Updated for v4 as `@tailwindcss/forms` |

### Requires Updates

| Plugin | Status | Notes |
|--------|--------|-------|
| **DaisyUI** | Updated | Check DaisyUI docs for v4-compatible version |
| **Flowbite** | Updated | Requires latest version for v4 support |
| **Custom JS plugins** | Migrate | Use `@plugin` directive or `@config` bridge |

### Using Legacy Plugins

If a plugin has not been updated for tailwind v4, use the `@config` bridge:

```css
@import "tailwindcss";
@config "../tailwind.config.js";
```

Then keep the plugin registration in your JavaScript config. This runs the legacy plugin system alongside the new engine. It is a bridge, not a permanent solution. Plan to remove it once the plugin ships a v4-compatible version.

## Before and After: Code Comparisons

Seeing the differences side by side makes the v4 changes concrete.

### Project Configuration

**v3 project structure:**
```
tailwind.config.js      # JavaScript config
postcss.config.js       # PostCSS with tailwindcss plugin
src/styles/globals.css  # @tailwind directives
```

**v4 project structure:**
```
postcss.config.js       # PostCSS with @tailwindcss/postcss (optional)
src/styles/globals.css  # @import + @theme
```

### Custom Colors

**v3:**
```js
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#eef2ff',
          500: '#6366f1',
          900: '#312e81',
        }
      }
    }
  }
}
```

**v4:**
```css
@theme {
  --color-brand-50: #eef2ff;
  --color-brand-500: #6366f1;
  --color-brand-900: #312e81;
}
```

Usage remains identical in both versions: `bg-brand-500`, `text-brand-50`, etc.

### Custom Components

**v3:**
```css
@layer components {
  .btn-primary {
    @apply px-6 py-3 bg-blue-500 text-white rounded-lg
           hover:bg-blue-600 transition-colors;
  }
}
```

**v4:**
```css
@layer components {
  .btn-primary {
    @apply px-6 py-3 bg-blue-500 text-white rounded-lg
           hover:bg-blue-600 transition-colors;
  }
}
```

`@apply` works the same way inside `@layer` blocks. The difference is that v4 uses native CSS cascade layers, giving you guaranteed specificity ordering.

### Dark Mode

**v3 (with config):**
```js
// tailwind.config.js
module.exports = {
  darkMode: 'class',
}
```

**v4 (with CSS):**
```css
@import "tailwindcss";

@variant dark (&:where(.dark, .dark *));
```

Usage in HTML remains the same: `dark:bg-gray-900`, `dark:text-white`.

## Common Migration Errors and Fixes

These are the issues developers hit most frequently when upgrading to v4.

### "It looks like you're trying to use tailwindcss directly as a PostCSS plugin"

**Cause:** Your `postcss.config.js` still references `tailwindcss` instead of `@tailwindcss/postcss`.

**Fix:**
```js
// Replace this:
plugins: { tailwindcss: {} }

// With this:
plugins: { '@tailwindcss/postcss': {} }
```

### Missing default styles or broken dark mode

**Cause:** Incomplete config migration. Default values changed between v3 and v4.

**Fix:** Explicitly define any v3 defaults you relied on in your `@theme` block. For dark mode, add the `@variant dark` directive if you use the class strategy.

### Utility classes being purged unexpectedly

**Cause:** Automatic content detection is missing some files. This can happen with files outside the project root or dynamically generated class names.

**Fix:** Add explicit source paths:
```css
@source "../packages/shared-ui/**/*.tsx";
@source "./data/**/*.json";
```

### Plugin errors after upgrade

**Cause:** JavaScript plugins reference v3 API methods that no longer exist.

**Fix:** Add the `@config` bridge temporarily:
```css
@import "tailwindcss";
@config "../tailwind.config.js";
```

Then update or replace the plugin when a v4-compatible version ships.

### Border colors changed

**Cause:** Default border color changed from `gray-200` to `currentColor` in v4.

**Fix:** Add explicit border colors where you previously relied on the default:
```html
<!-- Add explicit color where borders look wrong -->
<div class="border border-gray-200">...</div>
```

Or set the default globally:
```css
@layer base {
  *,
  ::after,
  ::before {
    border-color: var(--color-gray-200);
  }
}
```

## Should You Upgrade Now?

The decision to migrate to tailwind 4 depends on your project's circumstances.

### Upgrade now if:

- **You are starting a new project.** There is zero reason to start with v3 in 2026. Version 4 is the default
- **Build performance matters.** If your dev server or CI pipeline is slow because of Tailwind processing, the 2-5x speedup is worth the migration effort
- **You want CSS-first configuration.** The `@theme` approach is simpler, more portable, and eliminates the JavaScript config dependency
- **You need container queries or 3D transforms.** These features are only available in v4
- **Your project uses standard plugins.** If you rely on Headless UI, Radix, shadcn/ui, or official Tailwind plugins, compatibility is confirmed

### Wait if:

- **Critical plugins have not been updated.** If your project depends on niche plugins without v4 support, wait for those updates
- **You are mid-sprint on a deadline.** Migration is low-risk but not zero-risk. Do it during a dedicated maintenance window
- **Your project is in maintenance mode.** If you are not actively developing the project, the migration effort has no payback

For most teams, the answer is to upgrade. The tailwind 4 changes deliver immediate performance benefits, the migration tool handles the heavy lifting, and the new CSS-first workflow is genuinely better for day-to-day development.

{{ partial:cta/forge }}

## Conclusion

Tailwind CSS 4.0 is a generational upgrade. The Oxide engine makes builds measurably faster. CSS-first configuration eliminates the JavaScript config file and makes your design tokens native CSS custom properties. Automatic content detection removes an entire class of configuration bugs. And new features like container queries and 3D transforms expand what you can build with utility classes alone.

The migration path from v3 is well-supported. Run `npx @tailwindcss/upgrade`, update your build configuration, convert your theme to `@theme`, and test. Most projects complete the migration in 1 to 2 hours. Complex projects with custom plugins may take a day.

Start with the upgrade tool. Fix any build errors. Verify your UI visually. Then delete your `tailwind.config.js` and enjoy the faster, cleaner workflow that tailwind 4 delivers.

---

## Related Resources

- [Tailwind CSS vs Bootstrap: Which to Choose?](/blog/tailwind-vs-bootstrap)
- [Best Tailwind Component Libraries](/blog/best-tailwind-component-libraries)
- [Best Tailwind Templates](/blog/best-tailwind-templates)
- [Tailwind + Next.js Setup Guide](/blog/tailwind-nextjs-setup)
- [Vercel vs Railway: Best Deployment Platform](/blog/vercel-vs-railway)
- [Prisma vs Drizzle: Which ORM for Your Next.js Project?](/blog/prisma-vs-drizzle)
- [Best Next.js SaaS Templates](/blog/best-nextjs-saas-templates)
