Contents

Tailwind CSS v4: What Changed and How to Migrate

Tailwind CSS v4 is a ground-up rewrite. The JavaScript-based PostCSS plugin is gone, replaced by a Rust-powered engine called Oxide. Configuration moves from tailwind.config.js into CSS-native @theme directives. Full builds run up to 5x faster, incremental builds over 100x faster. The entry point is now a single @import "tailwindcss" line instead of three separate @tailwind directives. Most v3 projects can migrate in under an hour using the official @tailwindcss/upgrade codemod, but knowing what actually changed - and why - prevents surprises during the transition.

Released on January 22, 2025, Tailwind CSS v4.0 has since been followed by v4.1 and v4.2 (February 2026), which added a webpack plugin, new color palettes, and logical property utilities. The core architecture, though, was established in v4.0 and remains the foundation for everything that followed.

Tailwind CSS v4.0 release announcement card
The official Tailwind CSS v4.0 announcement from January 2025

What the Oxide Engine Changes Under the Hood

The most significant change in Tailwind v4 is invisible to your markup. Oxide is a Rust-compiled CSS engine that handles content detection, CSS generation, and optimization in a single pass. It replaces three separate JavaScript components from v3: the PostCSS plugin, the JIT compiler, and the content scanner.

Tailwind CSS v4 announcement showing the new version release with Oxide engine
Tailwind CSS v4 — powered by the new Rust-based Oxide engine
Image: Tailwind CSS

The performance difference is measurable. According to benchmarks from the Tailwind team against their own Catalyst project:

Metricv3.4v4.0Improvement
Full build378ms100ms3.78x
Incremental rebuild (new CSS)44ms5ms8.8x
Incremental rebuild (no new CSS)35ms192us182x

That last row is the one that matters most in daily work. When you are writing markup and reusing classes you have already used before - flex, col-span-2, font-bold - the incremental build completes in microseconds. HMR becomes effectively instant even on large codebases. Real-world reports from developers working on production projects describe build times dropping from 12 seconds to 1.2 seconds, with hot module replacement going from 500ms to under 100ms.

The speed comes from native CSS parsing. Oxide reads and generates CSS without converting to and from JavaScript ASTs, eliminating serialization overhead. Parse speeds are roughly twice as fast as other tools like SWC and drastically faster than the old PostCSS implementation.

Beyond raw speed, Oxide changes how Tailwind integrates with your build tool. In v3, Tailwind was exclusively a PostCSS plugin. In v4, you have three options:

  • The Vite plugin (@tailwindcss/vite) is the recommended path, offering the tightest integration and best performance
  • The PostCSS plugin (@tailwindcss/postcss) provides backward compatibility with existing PostCSS pipelines
  • The standalone CLI (npx @tailwindcss/cli -i input.css -o output.css) works for projects that do not use a JavaScript bundler, including Hugo sites and other static site generators

Automatic content detection is another benefit of the new engine. Oxide scans your project for source files automatically - HTML, JSX, TSX, Vue, Svelte, PHP, and others - without requiring a content array in your config. It uses heuristics and your .gitignore to determine which files to scan, eliminating the single most common v3 misconfiguration.

The tailwindcss npm package in v4 ships with the Rust binary for your platform via optional dependencies. Total install size is around 15 MB, compared to v3’s roughly 45 MB with all its JavaScript dependencies.

Tailwind CSS GitHub repository showing the project description and stats
The tailwindlabs/tailwindcss repository on GitHub

CSS-Native Configuration with @theme and @variant

The biggest developer-facing change is that configuration moves from JavaScript into your CSS. The shift affects how you define, reference, and override design tokens at every level of your project.

The entry point changes first. Replace the three v3 directives:

/* v3 - remove this */
@tailwind base;
@tailwind components;
@tailwind utilities;

With a single import:

/* v4 */
@import "tailwindcss";

This single line imports the entire framework including preflight, utilities, and your theme.

The @theme directive replaces the theme.extend object from tailwind.config.js. Instead of writing JavaScript:

// v3 tailwind.config.js - no longer needed
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: '#3b82f6',
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif'],
      },
    },
  },
}

You write CSS:

/* v4 - directly in your CSS file */
@theme {
  --color-brand: #3b82f6;
  --font-sans: "Inter", sans-serif;
  --breakpoint-3xl: 1920px;
}

Every theme value in v4 is a CSS custom property. This means you can reference them anywhere with var(--color-brand) and change them dynamically at runtime. In v3, the theme() function only worked at build time. Now your design tokens are live CSS variables available to any stylesheet, JavaScript, or browser devtools.

Custom variants replace the addVariant plugin API:

@variant hocus (&:hover, &:focus);

This creates a hocus: modifier usable on any utility class. Custom utilities replace the addUtilities API:

@utility tab-4 {
  tab-size: 4;
}

This generates tab-4 as a utility with full responsive and state variant support.

Plugins also move into CSS. Instead of require() calls in your config file, you import them directly:

@plugin "tailwindcss-animate";

The plugin format has changed between v3 and v4, so check each plugin’s documentation for compatibility. The official first-party plugins - @tailwindcss/typography, @tailwindcss/forms, and @tailwindcss/aspect-ratio - all have v4-compatible versions available.

Breaking Changes and Renamed Utilities

While v4 preserves most of v3’s utility class names, several have been renamed, removed, or changed behavior. The @tailwindcss/upgrade codemod handles roughly 90% of these automatically, but you should know what to look for during code review.

The most widespread breaking change is the gradient utility rename. All bg-gradient-to-* utilities become bg-linear-to-*, following CSS-native linear-gradient naming conventions. This affects virtually every project that uses Tailwind gradient backgrounds. The rename also enables new capabilities: you can now use angle-based gradients like bg-linear-45 for a 45-degree gradient.

Several other utilities have been removed or renamed:

v3 Utilityv4 Replacement
decoration-clone / decoration-slicebox-decoration-clone / box-decoration-slice
overflow-ellipsistext-ellipsis
flex-grow-* / flex-shrink-*grow-* / shrink-*
bg-opacity-*, text-opacity-*Slash syntax: bg-blue-500/75
transform (prefix class)Removed - transforms apply automatically

The default border color changed. In v3, border applied border-color: currentColor. In v4, the default border color is --color-gray-200 (a light gray). This will change the appearance of any borders that relied on inheriting the text color. Check your UI after migration for unexpected color shifts.

The ring utility now defaults to a 1px ring instead of 3px. If you relied on the old default, use ring-3 to get the same visual result.

Gradient color interpolation also changed. Gradients in v4 are interpolated in the oklab color space by default, which produces more perceptually uniform transitions between colors. Gradients that looked fine in v3 may appear slightly different. The via color stop behavior has also been adjusted, so test gradient-heavy UIs carefully.

Container queries no longer need a plugin. The @tailwindcss/container-queries v3 plugin is replaced by built-in support, with @min-* and @max-* variants using a new syntax.

Step-by-Step Migration from Tailwind v3 to v4

Follow these steps in order. Create a new branch before starting so you can review all changes in a single diff.

Start by running the upgrade codemod:

npx @tailwindcss/upgrade

The tool scans your project, updates class names in your HTML/JSX/TSX files, converts tailwind.config.js to @theme directives in your CSS, and updates package.json dependencies. It requires Node.js 20 or higher. Review all changes before committing - the tool handles roughly 90% of mechanical changes but may miss edge cases in dynamically constructed class names.

Next, update your build tool integration. The path depends on your stack. If you use Vite, replace tailwindcss and autoprefixer PostCSS plugins with @tailwindcss/vite in vite.config.ts - this is the recommended integration for new projects. For Next.js, update to version 15+, which has built-in Tailwind v4 support. Webpack users can use the @tailwindcss/webpack plugin, available since v4.2. If you run Hugo or another non-JS static site generator, switch to @tailwindcss/cli and run npx @tailwindcss/cli -i input.css -o output.css as part of your build pipeline.

Then replace the CSS entry point. Change the three @tailwind directives to @import "tailwindcss" in your main CSS file. If you were using @tailwind base; @tailwind components; @tailwind utilities;, replace all three with the single import.

Migrate tailwind.config.js to @theme next. The codemod handles most of this, but verify the output manually:

  • theme.extend.colors becomes @theme { --color-*: ... }
  • theme.extend.fontFamily becomes @theme { --font-*: ... }
  • Custom screens and breakpoints become @theme { --breakpoint-*: ... }
  • Delete tailwind.config.js when done

Update your plugins by replacing require() calls in your old config with @plugin imports in CSS. Check each plugin’s GitHub repository for v4 compatibility:

  • DaisyUI requires upgrading to v5 (v4 of DaisyUI is incompatible with Tailwind v4)
  • Shadcn/ui has been fully compatible since January 2026, and the CLI generates v4-compatible components automatically
  • Flowbite v3 includes full Tailwind v4 support

Finally, run visual regression testing. Compare screenshots of key pages before and after migration. Percy, Playwright visual comparison, or even manual side-by-side screenshots all work. The most common visual regressions come from the border color default change and the ring width change.

New Features Worth Adopting After Migration

Beyond fixing what broke, v4 introduces features that improve your workflow and CSS output. Several are worth adopting right away.

Since all theme values are now CSS custom properties, you can implement dark mode by redefining variables directly:

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #0a0a0a;
    --color-text: #e5e5e5;
  }
}

This works alongside or instead of Tailwind’s dark: variant, giving you more control over theme switching.

Container queries are now first-party. You can style components based on their container’s width instead of the viewport using @sm:text-lg and @lg:grid-cols-3 as responsive modifiers - no plugin needed, no configuration.

The @starting-style variant is new. CSS @starting-style lets you animate elements from display: none, which means pure-CSS modal and dropdown open animations without JavaScript. Tailwind v4 exposes this through a variant.

v4 also adds @property support. You can define custom CSS properties with types and initial values, which makes it possible to smoothly transition CSS variables that were previously not animatable:

@property --gradient-start {
  syntax: "<color>";
  inherits: false;
  initial-value: blue;
}

Arbitrary value syntax got a small but welcome improvement: bg-[--my-var] now works without var() wrapping. Tailwind detects CSS variable references automatically.

3D transform utilities like rotate-x-45, rotate-y-90, and perspective-500 are built in, replacing the need for arbitrary values or custom plugins.

The gradient system expanded too. Beyond the rename from bg-gradient to bg-linear, v4 adds radial gradients (bg-radial), conic gradients (bg-conic), and gradient interpolation modes. These were previously only available through arbitrary values or third-party plugins.

Common Migration Pitfalls

A few issues come up repeatedly in real-world migrations that the codemod does not catch automatically.

Dynamic class names are the most common blind spot. If you build class names through string concatenation or template literals - bg-${color}-500 - the upgrade tool cannot rewrite these. Search your codebase for patterns like this and update them manually. The gradient rename (bg-gradient to bg-linear) is the most frequent case.

Third-party component libraries need attention too. If you use a library that ships pre-built Tailwind classes, check whether it has released a v4-compatible version before upgrading. Upgrading Tailwind without upgrading the library can result in broken styles.

PostCSS config conflicts trip people up when their pipeline includes other plugins that process Tailwind’s output. The new Oxide engine may interact differently with them. The Vite plugin path avoids most of these issues since it bypasses PostCSS entirely.

CSS specificity has changed because Tailwind v4 uses native CSS cascade layers (@layer theme, base, components, utilities). If you have custom CSS that relied on v3’s specificity behavior, the cascade layer boundaries may cause your overrides to behave differently. Use the @layer directive in your custom CSS to place rules in the correct layer.

Check your Node.js version before starting. The upgrade tool and v4 itself require Node.js 20 or higher. If your CI pipeline or development environment runs an older version, update it first.

For most projects, the codemod handles the mechanical work and the remaining effort is visual testing plus fixing edge cases that automated tooling cannot detect. Budget an hour or two for a typical project, longer if you have a lot of custom plugins or dynamic class construction. The build speed difference alone makes the migration worth doing sooner rather than later.