Tailwind CSS v4 — Everything That Changed and How to Migrate

Sanjeev SharmaSanjeev Sharma
4 min read

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/upgrade

Key breaking changes:

v3v4
tailwind.config.js@theme in CSS
@tailwind base/components/utilities@import "tailwindcss"
bg-opacity-50bg-black/50
ring-opacity-50ring-black/50
divide-opacity-50divide-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.js and @theme during 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/postcss for v4

Best Practices

  • Start new projects directly with v4 — skip v3 entirely
  • Use @theme variables 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.js to @theme blocks in CSS files — the JS config file is gone
  • All theme values in @theme become real CSS custom properties, usable anywhere in CSS with var()
  • The new single import @import "tailwindcss" replaces the three-line @tailwind directive setup
  • Opacity utilities (bg-opacity-*) are removed — use slash syntax (bg-black/50) instead
  • npx @tailwindcss/upgrade automates most of the migration from v3
  • New utilities include subgrid, field-sizing, not-* variant, and starting: for initial render animations
  • Theme values become utility class names automatically — --color-brand-500 generates text-brand-500, bg-brand-500, etc.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading