Switch Language
Toggle Theme

Tailwind CSS v4 Features Deep Dive: Performance, Configuration, and Migration Guide

35 milliseconds. That’s how long Tailwind v3 takes for an incremental build—seems fast, right? But in v4, the same operation takes just 192 microseconds. Not milliseconds—microseconds. That’s nearly 200 times faster.

Honestly, when I first saw this number, I was skeptical. Marketing claims are easy to make. But after running the migration myself and watching HMR drop from 340ms to 12ms, I realized—Tailwind is serious this time.

This is the real impact of the Rust-based Oxide engine rewrite. Not just a “feels a bit faster” psychological effect, but the kind of experience where you change a padding value and the page refreshes before you can blink. Tailwind v4 isn’t just about swapping engines—it completely transforms the configuration approach (moving from JS to CSS), installation workflow (zero config), and utility syntax (opacity modifiers, Container Queries, 3D Transforms…).

In this article, I’ll break down these changes clearly and provide you with a migration checklist you can follow step by step.

The Oxide Engine—Why is v4 So Fast?

Have you ever experienced this scenario: you change a color value, save, and then stare at the browser for two or three seconds before seeing the change? In large projects, Tailwind v3’s incremental builds can definitely cause some anxiety—especially when you’re racing against a deadline.

v4’s Oxide engine addresses this pain point directly. The Tailwind team didn’t just patch up the old JS engine—they completely rewrote the compiler in Rust from scratch. This was a bold decision, but it brought tangible benefits.

How Much Performance Actually Improved?

The official team ran benchmarks using the Catalyst project, and the data is striking:

Test Scenariov3.4v4.0Improvement
Full build378ms100ms3.78x
Incremental (CSS changed)44ms5ms8.8x
Incremental (no CSS change)35ms192µs182x

That last number is the most impressive. When you only change HTML structure without adding new CSS classes, v4’s build time drops to microseconds—a delay you can’t even perceive.

Here’s another dataset from a production project (500+ component codebase):

Metricv3.4v4.0Change
Cold build12.3s1.8s85% faster
Dev server startup4.2s0.8s81% faster
HMR update340ms12ms96% faster
Production CSS size48KB31KB35% smaller
Memory usage180MB45MB75% less
96%
HMR speed improvement

Dropping HMR from 340ms to 12ms makes a huge impact on developer experience. Before, changing styles meant waiting for a “noticeable pause”—now it’s essentially instant.

What Did Oxide Do Right?

The core of the Oxide engine is Rust + Lightning CSS. Everyone knows Rust’s performance advantages, but the architectural changes are even more important:

Unified toolchain. In the v3 era, Tailwind relied on the PostCSS ecosystem, requiring additional configuration for autoprefixer, cssnano, and other tools. v4 built all of these in, depending only on the Lightning CSS package. Fewer middlemen naturally means faster performance.

Intelligent content detection. Previously, you had to manually configure the content array in tailwind.config.js to tell Tailwind which files to scan for class names. v4 reads .gitignore and the module graph directly to automatically discover files that need scanning. Less configuration, and faster too.

Native CSS features. v4 uses real @layer rules, @property custom properties, color-mix() color functions, and other modern CSS features. These are natively supported by browsers and don’t need compile-time transformations.

Honestly, you don’t need to fully understand these technical details. What matters is the result: faster builds, less configuration, better developer experience.

CSS-first Configuration—Say Goodbye to tailwind.config.js

This is the biggest change in v4, and also the part with the highest migration cost.

Previously we wrote configuration in tailwind.config.js. Now we need to move it to CSS files using the @theme directive. Honestly, I wasn’t used to it at first—after writing JS configs for years, suddenly writing CSS variables felt awkward. But after using it for a while, I realized it actually fits better with CSS’s way of thinking.

Configuration Migration: Before & After

Let’s look at the simplest example. Customizing a primary color:

// v3: tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#3b82f6',
      },
    },
  },
}

In v4, this all goes into CSS:

/* v4: app.css */
@import "tailwindcss";

@theme {
  --color-primary: #3b82f6;
}

You might notice the variable name changed from primary to --color-primary. This is because v4 has strict naming prefix rules.

Variable Naming Rules Reference Table

v3 Config Itemv4 CSS Variable PrefixExample
colors—color-*—color-primary: #3b82f6
spacing—spacing-*—spacing-128: 32rem
fontSize—text-*—text-xs: 0.75rem
fontFamily—font-*—font-sans: “Inter”
borderRadius—radius-*—radius-lg: 0.5rem
screens—breakpoint-*—breakpoint-md: 768px
boxShadow—shadow-*—shadow-card: 0 4px 12px rgba(0,0,0,0.1)
animation—animate-*—animate-spin: spin 1s linear infinite

This prefix system feels tedious at first, but it has a benefit: you don’t have to guess variable names. Want to know how to write a font size variable? It definitely starts with --text-.

A More Complex Configuration Example

Let’s look at a more complete example. Say your v3 config looks like this:

// v3: tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          light: '#f0f9ff',
          DEFAULT: '#0ea5e9',
          dark: '#0369a1',
        },
      },
      fontFamily: {
        display: ['Cal Sans', 'sans-serif'],
      },
      animation: {
        'fade-in': 'fadeIn 0.5s ease-out',
      },
    },
  },
  plugins: [
    require('@tailwindcss/typography'),
  ],
}

Migrating to v4:

/* v4: app.css */
@import "tailwindcss";

@theme {
  /* Colors */
  --color-brand-light: #f0f9ff;
  --color-brand: #0ea5e9;
  --color-brand-dark: #0369a1;

  /* Fonts */
  --font-display: "Cal Sans", sans-serif;

  /* Animations */
  --animate-fade-in: fadeIn 0.5s ease-out;
}

/* Plugins */
@plugin "@tailwindcss/typography";

Notice a few changes:

  1. Color nesting flattened. v3 supported nested objects brand.light, v4 requires --color-brand-light
  2. Plugins use @plugin. No need to require in JS anymore
  3. DEFAULT suffix removed. The default color for brand is now just --color-brand

Dark Mode Configuration

v3’s dark mode configuration looked like this:

// v3
module.exports = {
  darkMode: 'class', // or 'media'
}

v4 defaults to the media strategy (follows system preference). If you want to change to class, add one line in CSS:

/* v4 */
@import "tailwindcss";
@variant dark (&:where(.dark, .dark *));

This code means: when an element or its parent has the .dark class, apply dark mode styles.

Content Detection Configuration

Previously you had to manually specify which files to scan:

// v3
module.exports = {
  content: [
    './src/**/*.{js,ts,jsx,tsx}',
    './public/index.html',
  ],
}

v4 automatically reads .gitignore, ignores files you’ve excluded, then scans all relevant files in your project. If your project structure is unusual, you can manually add paths with @source:

/* v4 */
@import "tailwindcss";
@source "../node_modules/my-ui-lib";

The biggest benefit of CSS-first configuration: all style-related configuration is in one CSS file, no jumping back and forth between JS and CSS. The downside is the learning curve, especially for developers used to JS configuration.

Installation and Integration—A Zero-Config New Experience

Anyone who has installed Tailwind v3 knows the workflow: install three packages, create config file, configure PostCSS, write content array… While not overly complex, you have to go through it for every new project.

v4 simplifies this process to the extreme.

Minimal Installation

If you use Vite, you only need two steps:

# 1. Install
npm install tailwindcss @tailwindcss/vite

# 2. Add one line to vite.config.js
import tailwindcss from '@tailwindcss/vite'

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

Then add one import line to your CSS file:

/* app.css or index.css */
@import "tailwindcss";

That’s it. No tailwind.config.js, no postcss.config.js, no content array. Works out of the box.

Three Integration Methods

Vite Integration (recommended):

npm install tailwindcss @tailwindcss/vite
// vite.config.js
import tailwindcss from '@tailwindcss/vite'

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

PostCSS Integration:

npm install tailwindcss @tailwindcss/postcss
// postcss.config.js
export default {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}

CLI Usage:

npx tailwindcss -i input.css -o output.css --watch

For most modern projects, the Vite integration is sufficient. The PostCSS approach is suitable for migrating older projects, while CLI is for scenarios without a build pipeline.

How Automatic Content Detection Works

v4 doesn’t require you to configure a content array—it automatically discovers files that need scanning. How?

It reads your .gitignore file to exclude directories that don’t need scanning (node_modules, dist, etc.), then searches for Tailwind class names in the remaining files.

This mechanism has a prerequisite: your project should have a standard Node.js structure. If you put template files in unusual locations, like a folder outside the project root, you need to specify manually:

@import "tailwindcss";
@source "../templates";  /* Manually add scan path */

Automatic detection has another benefit: no config updates for new files. Previously, every time you created a new component file, you had to update the content array in tailwind.config.js (unless you used wildcards **/*, but some project structures don’t support that). Now you don’t have to worry about it—new files just work.

Breaking Changes and Migration Checklist

This is the core of migration. Don’t panic—most changes follow patterns, and there’s an official automated migration tool.

Opacity Modifier Changes

This is the most impactful change. In v3, setting background opacity required:

<!-- v3 -->
<div class="bg-blue-500 bg-opacity-50">...</div>

v4 integrates opacity into the color value:

<!-- v4 -->
<div class="bg-blue-500/50">...</div>

Not just background colors—all color-related properties support this syntax:

<!-- Text color opacity -->
<p class="text-gray-900/75">...</p>

<!-- Border color opacity -->
<div class="border-red-500/30">...</div>

What about class names like bg-opacity-*? Just remove them. v4 no longer supports them.

Renamed Utility Classes

v4 renamed some utility classes, mainly for simplification and semantic adjustment:

v3 Class Namev4 Class NameNotes
flex-growgrowSimplified naming
flex-grow-*grow-*Simplified naming
flex-shrinkshrinkSimplified naming
flex-shrink-*shrink-*Simplified naming
overflow-ellipsistext-ellipsisCategory adjustment
decoration-slicebox-decoration-sliceCategory adjustment
shadow-smshadow-xsSize renaming
shadowshadow-smSize renaming
rounded-smrounded-xsSize renaming
roundedrounded-smSize renaming
outline-noneoutline-hiddenSemantic change

Note the shadow and rounded changes: the original shadow became shadow-sm, and the original shadow-sm became shadow-xs. This is a semantic adjustment to make size naming more consistent.

Default Value Changes

Several default values changed, which may cause style inconsistencies:

Border default color: v3 used gray-200, v4 changed to currentColor. This means border colors will follow text color.

<!-- v3: border is gray -->
<div class="border text-blue-500">Border is gray-200</div>

<!-- v4: border follows text color -->
<div class="border text-blue-500">Border is blue-500</div>

Ring default value: v3 default width 3px, color blue-500; v4 default width 1px, color currentColor.

<!-- v3: ring is blue 3px -->
<button class="ring">...</button>

<!-- v4: ring is currentColor 1px -->
<button class="ring">...</button>

<!-- For v3 effect -->
<button class="ring-3 ring-blue-500">...</button>

Complete Migration Checklist

Execute in order:

  1. Upgrade dependencies

    npm install tailwindcss@latest @tailwindcss/vite@latest
  2. Run automatic migration tool

    npx @tailwindcss/upgrade

    This tool automatically converts most syntax, including @tailwind directives, opacity class names, renamed utility classes, etc.

  3. Convert CSS entry file

    /* v3 */
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    /* v4 */
    @import "tailwindcss";
  4. Migrate config file: Move configuration from tailwind.config.js to the CSS @theme block.

  5. Update plugins: Change JS plugins to @plugin syntax.

    @plugin "@tailwindcss/typography";
  6. Check opacity class names: Globally search for bg-opacity, text-opacity, border-opacity, replace with new syntax.

  7. Check renamed class names: Focus on shadow-*, rounded-*, flex-grow-*, flex-shrink-*.

  8. Check default value changes: Pay special attention to border colors and ring styles.

  9. Test visual regression: Run your visual tests to ensure no missed changes.

The automatic migration tool handles 80% of the work, but the remaining 20% still requires manual review. Especially those default value changes—the tool can’t change your expectations, only you can adjust them.

New Features—Container Queries, 3D Transforms, and More

Besides performance and configuration improvements, v4 also brings some practical new features.

Built-in Container Queries Support

Previously you needed a plugin for Container Queries—now it’s natively supported:

<!-- Define container -->
<div class="@container">
  <!-- Responsive based on container width -->
  <div class="@md:grid-cols-2 @lg:grid-cols-3">
    ...
  </div>
</div>

@container is equivalent to container-type: inline-size, and @md: is the container query breakpoint. The syntax is similar to responsive breakpoints md:, just with @ prepended.

This is particularly suitable for component libraries—component styles can adapt based on parent container width rather than screen width.

3D Transform Utilities

v4 added a set of 3D transform utilities:

<!-- 3D perspective -->
<div class="perspective-distant">
  <!-- X-axis rotation -->
  <div class="rotate-x-45">...</div>
</div>

<!-- Y-axis rotation -->
<div class="rotate-y-12">...</div>

<!-- Z-axis scale -->
<div class="scale-z-150">...</div>

Available classes include rotate-x-*, rotate-y-*, rotate-z-*, scale-z-*, perspective-*, translate-z-*, etc. Making card flip effects, 3D menus, and similar interactions is much easier now.

@starting-style Variant

This works with CSS native @starting-style feature, for styles when elements first render:

<!-- Element fades from transparent to opaque on appearance -->
<div class="starting:opacity-0 opacity-100 transition-opacity">
  ...
</div>

No JavaScript needed for entrance animations. Previously you had to use custom animations like animate-fade-in, now one class name does it.

not-* Variant

Supports CSS :not() pseudo-class:

<!-- All child elements except the last -->
<li class="not-last:mb-4">...</li>

<!-- All buttons without disabled state -->
<button class="not-disabled:opacity-100">...</button>

Previously you had to use the inverse approach like last:mb-0, now not-* is more intuitive.

Extended Gradient API

v4’s gradients are more powerful:

<!-- Conic gradient -->
<div class="bg-conic/from-red-500 via-yellow-500 to-blue-500">...</div>

<!-- Radial gradient -->
<div class="bg-radial from-white to-transparent">...</div>

<!-- Gradient interpolation mode -->
<div class="bg-linear-to-r from-blue-500 to-purple-500 via-oklch">...</div>

via-oklch is the new interpolation mode, making gradient transitions more natural (especially during color space conversions).

Conclusion

After all this, should you upgrade?

If your project is still in active development, my recommendation is: yes. Dropping HMR from hundreds of milliseconds to about ten milliseconds saves you a lot of waiting time every day. Plus reduced CSS size and lower memory usage—it’s especially worthwhile for large projects.

Migration cost is mainly in configuration conversion and class name changes. Fortunately, there’s the automatic migration tool npx @tailwindcss/upgrade that handles most repetitive work. I tried it—a medium-sized project (200+ components) takes about half a day to migrate, including testing.

For new projects, use v4 directly. Zero-config installation, automatic content detection, CSS-first configuration—these changes make Tailwind even better to use, there’s no reason not to.

One thing to note about browser compatibility: v4 requires Safari 16.4+, Chrome 111+, Firefox 128+. If your project needs to support older browsers, you might want to wait.

Action items:

  • New projects: use v4 directly
  • Existing projects: use npx @tailwindcss/upgrade for automatic migration
  • Focus on checking border default color and ring default value changes
  • Check the official upgrade documentation if you encounter issues

Tailwind CSS v4 Migration Guide

Complete step-by-step instructions for upgrading from Tailwind v3 to v4

⏱️ Estimated time: 30 min

  1. 1

    Step1: Upgrade dependency packages

    Run the npm install command to upgrade to the latest version:

    ```bash
    npm install tailwindcss@latest @tailwindcss/vite@latest
    ```

    If using PostCSS integration, install:
    ```bash
    npm install tailwindcss@latest @tailwindcss/postcss@latest
    ```
  2. 2

    Step2: Run automatic migration tool

    Tailwind provides a one-click migration tool:

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

    This tool automatically handles:
    - @tailwind directive conversion to @import "tailwindcss"
    - Opacity class names from bg-opacity-* to /50 syntax
    - Renamed utility classes (shadow-sm → shadow-xs, etc.)
    - Config file conversion to CSS @theme format
  3. 3

    Step3: Convert CSS entry file

    Replace the original three @tailwind directives with one @import:

    ```css
    /* Remove these */
    @tailwind base;
    @tailwind components;
    @tailwind utilities;

    /* Replace with */
    @import "tailwindcss";
    ```
  4. 4

    Step4: Migrate custom config to CSS

    Move theme configuration from tailwind.config.js to CSS file:

    ```css
    @import "tailwindcss";

    @theme {
    /* Color configuration */
    --color-brand: #0ea5e9;

    /* Font configuration */
    --font-display: "Cal Sans", sans-serif;

    /* Animation configuration */
    --animate-fade-in: fadeIn 0.5s ease-out;
    }
    ```

    Note the variable naming rules: colors → --color-*, fontSize → --text-*
  5. 5

    Step5: Update plugin import method

    Use @plugin directive for JS plugins:

    ```css
    /* Old method: in tailwind.config.js */
    // plugins: [require('@tailwindcss/typography')]

    /* New method: in CSS file */
    @plugin "@tailwindcss/typography";
    ```
  6. 6

    Step6: Check default value changes

    Focus on two default value changes:

    - **border color**: Changed from gray-200 to currentColor
    - If you need the original gray border, explicitly add border-gray-200

    - **ring default value**: Changed from 3px blue-500 to 1px currentColor
    - If you need the original blue 3px ring, use ring-3 ring-blue-500
  7. 7

    Step7: Test and fix styles

    Run development server to check for visual changes:

    ```bash
    npm run dev
    ```

    Focus on checking:
    - Opacity-related styles are working correctly
    - shadow and rounded sizes match expectations
    - Border colors match design specs
    - Run project's visual regression tests (if available)

FAQ

What's the biggest difference between Tailwind CSS v4 and v3?
There are three core differences: First, the Oxide engine rewritten in Rust, improving HMR speed by 96%; second, configuration moved from JS to CSS using the @theme directive; third, simplified installation—Vite projects need only 2 steps of configuration.
How long does it take to migrate to Tailwind v4?
A medium-sized project (200+ components) takes about half a day to complete migration. The automatic migration tool handles 80% of the work, with the remaining 20% requiring manual review of default value changes and special configurations.
What are the browser compatibility requirements for v4?
v4 requires Safari 16.4+, Chrome 111+, Firefox 128+ because it uses native CSS features like @layer, @property, color-mix(), etc. Projects that need to support older browsers should postpone upgrading.
What can the automatic migration tool handle?
It automatically handles @tailwind directive conversion, opacity class name syntax updates (bg-opacity-50 → /50), renamed utility classes (shadow-sm → shadow-xs), and JS config to CSS @theme conversion. It cannot handle style differences caused by default value changes.
Does v4 still need to configure the content array?
No. v4 automatically reads the .gitignore file to exclude irrelevant directories, then scans for Tailwind class names in the project. Special cases can use the @source directive to manually add scan paths.
What if styles are inconsistent after upgrading from v3?
Focus on checking three default value changes: border default color changed from gray-200 to currentColor, ring default changed from 3px blue-500 to 1px currentColor, and shadow and rounded size naming adjustments. Explicitly add class names to affected elements to restore original styles.
What new features in v4 are worth noting?
Built-in Container Queries support (@container syntax), 3D Transform utilities (rotate-x/y/z, perspective-*), @starting-style variant for entrance animations, not-* variant, and extended Gradient API (conic, radial, oklch interpolation).

12 min read · Published on: Mar 25, 2026 · Modified on: Mar 25, 2026

Comments

Sign in with GitHub to leave a comment

Related Posts