Tailwind CSS v4: The Complete Guide for 2026

Tailwind CSS v4 is a ground-up rewrite of the most popular utility-first CSS framework. Released in January 2025, it replaces the JavaScript-based configuration system with a CSS-first approach, introduces a new high-performance engine built on Lightning CSS, and adds first-class support for modern CSS features like cascade layers, container queries, and 3D transforms. This guide covers everything you need to know to use Tailwind CSS v4 effectively and migrate existing projects from v3.

What Changed from Tailwind CSS v3 to v4

Tailwind CSS v4 is not an incremental update. It is a complete rewrite with fundamental architectural changes. Here are the most significant differences:

Installation and Setup

Tailwind CSS v4 simplifies installation dramatically. The setup depends on your build tool.

Vite (Recommended)

For Vite-based projects (React, Vue, Svelte, or vanilla), install the first-party Vite plugin:

# Install Tailwind CSS and the Vite plugin
npm install tailwindcss @tailwindcss/vite

Add the plugin to your Vite config:

// vite.config.js
import tailwindcss from "@tailwindcss/vite";

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

Then add the import to your main CSS file:

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

That is the entire setup. No tailwind.config.js, no PostCSS config, no content paths.

PostCSS

If your project uses PostCSS (e.g., with webpack or older build tools):

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

Standalone CLI

For projects without a JavaScript build tool:

npm install tailwindcss @tailwindcss/cli
# Build your CSS
npx @tailwindcss/cli -i app.css -o dist/app.css

v3 vs v4 Setup Comparison

/* === Tailwind CSS v3 === */

/* tailwind.config.js required */
/* postcss.config.js required */
/* content paths required */

/* app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;


/* === Tailwind CSS v4 === */

/* No config files needed */

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

CSS-First Configuration with @theme

The @theme directive is the heart of Tailwind CSS v4's configuration model. It replaces the theme object in tailwind.config.js with standard CSS custom properties that generate corresponding utility classes.

@import "tailwindcss";

@theme {
    --color-brand: #3b82f6;
    --color-brand-dark: #1d4ed8;
    --color-brand-light: #93c5fd;

    --font-family-display: "Inter", sans-serif;
    --font-family-mono: "Fira Code", monospace;

    --breakpoint-3xl: 1920px;

    --spacing-18: 4.5rem;
    --spacing-72: 18rem;
}

With this configuration, you can immediately use classes like bg-brand, text-brand-dark, font-display, 3xl:grid-cols-4, p-18, and w-72. Every variable defined in @theme maps to utility classes automatically.

Migrating tailwind.config.js to @theme

/* === v3: tailwind.config.js === */
module.exports = {
    theme: {
        extend: {
            colors: {
                brand: {
                    DEFAULT: "#3b82f6",
                    dark: "#1d4ed8",
                },
            },
            fontFamily: {
                display: ["Inter", "sans-serif"],
            },
            spacing: {
                18: "4.5rem",
            },
        },
    },
};


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

@theme {
    --color-brand: #3b82f6;
    --color-brand-dark: #1d4ed8;
    --font-family-display: "Inter", sans-serif;
    --spacing-18: 4.5rem;
}

@theme vs :root

Use @theme when you want a CSS variable to generate utility classes. Use :root for regular CSS variables that should not have corresponding utilities:

@theme {
    --color-primary: #3b82f6;  /* Creates bg-primary, text-primary, etc. */
}

:root {
    --sidebar-width: 280px;    /* No utility class generated */
    --header-height: 64px;     /* Just a regular CSS variable */
}

Loading a JavaScript Config (Gradual Migration)

If you need to keep a JavaScript config during migration, use the @config directive:

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

This loads your existing config and is useful for projects with complex plugins or programmatic theme generation. The long-term goal is to migrate everything to @theme in CSS.

Zero-Config Content Detection

In Tailwind CSS v3, you had to specify which files to scan for class names:

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

In v4, automatic content detection scans your entire project for template files, respecting .gitignore to skip node_modules, build output, and other ignored directories. No configuration is needed in the vast majority of projects.

If you need to include files from outside your project root or from a gitignored directory, use the @source directive:

@import "tailwindcss";

/* Include templates from a shared package */
@source "../shared-components/src";

Native CSS Cascade Layers

Tailwind CSS v4 uses real CSS @layer rules to organize its output. This gives you explicit control over style precedence without fighting specificity.

/* Tailwind v4 generates this layer structure: */
@layer theme, base, components, utilities;

/* theme    — CSS custom properties from @theme */
/* base     — Reset and base styles (preflight) */
/* components — Reusable component classes */
/* utilities  — All utility classes (highest precedence) */

Because utilities are in the highest-precedence layer, they always override component and base styles regardless of source order. This is a major improvement over v3, where specificity conflicts between utilities and component libraries were a common source of frustration.

You can add your own styles to these layers:

@import "tailwindcss";

@layer components {
    .btn {
        border-radius: 0.5rem;
        padding: 0.5rem 1rem;
        font-weight: 600;
    }
}

/* Or define a custom utility */
@utility text-balance {
    text-wrap: balance;
}

Note the new @utility directive: in v4, you define custom utilities with @utility instead of adding them inside @layer utilities. This ensures they work properly with variants like hover: and md:.

New Color System and Theming

Tailwind CSS v4 uses CSS custom properties for all theme values, which means colors can be overridden at runtime without rebuilding your CSS. The new color system also leverages color-mix() for opacity modifiers.

@theme {
    --color-primary: oklch(0.6 0.2 260);
    --color-secondary: oklch(0.7 0.15 150);
    --color-surface: oklch(0.15 0.01 260);
    --color-surface-bright: oklch(0.25 0.01 260);
}

You can use any CSS color format: hex, rgb, hsl, oklch, or oklab. The oklch color space is particularly useful because it provides perceptually uniform lightness, making it easier to build consistent color palettes.

Runtime Theme Switching

Because theme values are CSS custom properties, you can switch themes by overriding variables on a parent element:

/* Default theme */
@theme {
    --color-bg: #0f172a;
    --color-text: #e2e8f0;
    --color-accent: #3b82f6;
}

/* Alternative theme applied via a class */
.theme-warm {
    --color-bg: #1c1917;
    --color-text: #fafaf9;
    --color-accent: #f97316;
}
<!-- Switch themes by toggling the class -->
<body class="bg-bg text-text">
    <button class="bg-accent">Click</button>
</body>

Container Queries

Container queries are built into Tailwind CSS v4 without any plugin. They let you style elements based on their container's size rather than the viewport. This is essential for truly reusable components.

<!-- Mark a container -->
<div class="@container">
    <!-- Respond to the container's width -->
    <div class="grid grid-cols-1 @md:grid-cols-2 @lg:grid-cols-3">
        <!-- Cards -->
    </div>
</div>

The @sm, @md, @lg, @xl variants work just like responsive breakpoints but are relative to the nearest @container ancestor. You can also use arbitrary values and range queries:

<!-- Named container -->
<div class="@container/sidebar">
    <nav class="@sm/sidebar:flex-col @lg/sidebar:flex-row">
        <!-- Navigation items -->
    </nav>
</div>

<!-- Arbitrary container query value -->
<div class="@[400px]:grid-cols-2"></div>

<!-- Max-width container query -->
<div class="@max-md:text-sm"></div>

<!-- Range: between sm and lg -->
<div class="@min-sm:@max-lg:p-4"></div>

3D Transforms

Tailwind CSS v4 adds a full set of 3D transform utilities, eliminating the need for custom CSS or plugins for common 3D effects.

<!-- Rotate on the X and Y axes -->
<div class="rotate-x-12 rotate-y-6"></div>

<!-- 3D translation -->
<div class="translate-z-8"></div>

<!-- Scale on the Z axis -->
<div class="scale-z-150"></div>

<!-- Perspective on a parent -->
<div class="perspective-distant">
    <div class="rotate-y-12 backface-hidden">
        Card front
    </div>
</div>

<!-- Perspective utilities -->
<div class="perspective-near"></div>     <!-- 300px -->
<div class="perspective-normal"></div>   <!-- 500px -->
<div class="perspective-distant"></div>  <!-- 1000px -->

<!-- Perspective origin -->
<div class="perspective-origin-top-left"></div>

A practical card-flip example:

<div class="group perspective-normal w-64 h-40">
    <div class="relative w-full h-full transition-transform duration-500
                preserve-3d group-hover:rotate-y-180">
        <div class="absolute inset-0 bg-blue-600 backface-hidden
                    flex items-center justify-center rounded-lg">
            Front
        </div>
        <div class="absolute inset-0 bg-purple-600 backface-hidden
                    rotate-y-180 flex items-center justify-center rounded-lg">
            Back
        </div>
    </div>
</div>

Composable Variants

Tailwind CSS v4 makes variants fully composable. You can chain state variants like group-*, peer-*, has-*, and not-* in any combination:

<!-- Show when a group descendant has focus -->
<div class="group">
    <input type="text" />
    <p class="group-has-focus:visible">Hint text</p>
</div>

<!-- Style based on :not() -->
<div class="not-last:border-b">Item</div>

<!-- Combine variants freely -->
<button class="group-has-peer-not-disabled:opacity-100">
    Submit
</button>

<!-- The inert variant -->
<div class="inert:opacity-50 inert:pointer-events-none">
    Disabled section
</div>

New variant additions in v4 include not-* (negation), inert, nth-*, in-*, and the ability to stack any variant with group-* and peer-*.

Dark Mode

Tailwind CSS v4 keeps the dark: variant but simplifies the implementation. Dark mode uses the prefers-color-scheme media query by default:

<div class="bg-white dark:bg-gray-900">
    <p class="text-gray-900 dark:text-gray-100">
        Adapts to system preferences.
    </p>
</div>

To use class-based dark mode (for manual toggle), override the dark mode variant in your CSS:

@import "tailwindcss";

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

This replaces the darkMode: 'class' option from v3's config file. Now toggle dark mode by adding or removing the .dark class on <html> or <body>.

v3 vs v4 Dark Mode Comparison

/* === v3: tailwind.config.js === */
module.exports = {
    darkMode: "class",
};


/* === v4: app.css === */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

Typography Changes

The @tailwindcss/typography plugin still works in v4 but has been updated. The prose classes now integrate with the CSS-first theme system:

npm install @tailwindcss/typography
@import "tailwindcss";
@plugin "@tailwindcss/typography";

Note the new @plugin directive, which replaces the plugins array in tailwind.config.js. You can customize typography theme values using @theme:

@theme {
    --typography-body: 1.75;        /* Line height */
    --typography-headings: 700;     /* Font weight */
}

The not-prose class for excluding elements from prose styling continues to work as before.

Performance: The Lightning CSS Engine

Tailwind CSS v4's engine is a complete rewrite that uses Lightning CSS (written in Rust) instead of PostCSS for CSS processing. The performance improvements are substantial:

Because Lightning CSS handles vendor prefixing and modern syntax lowering, you can drop autoprefixer from your build pipeline entirely:

/* v3 postcss.config.js (can be removed in v4 Vite setup) */
module.exports = {
    plugins: {
        tailwindcss: {},
        autoprefixer: {},  /* No longer needed */
    },
};

Migration from v3 to v4: Step-by-Step

The Tailwind team provides an official upgrade tool that automates most of the migration. Here is the full process:

Step 1: Check Prerequisites

Step 2: Run the Upgrade Tool

npx @tailwindcss/upgrade

This tool performs three categories of changes:

  1. CSS migration — Converts @tailwind base/components/utilities to @import "tailwindcss", converts @layer utilities { ... } to @utility directives, and restructures import chains.
  2. Config migration — Extracts theme values and plugins from your JavaScript config and generates @theme and @plugin directives in CSS.
  3. Template migration — Updates class names in your HTML, JSX, Vue, and Svelte files. Examples: bg-opacity-50 becomes bg-black/50, !flex becomes flex!, bg-gradient-to-t becomes bg-linear-to-t.

Step 3: Update Your Build Tool

Switch from PostCSS to the Vite plugin if applicable:

# Remove old dependencies
npm uninstall autoprefixer postcss-import

# Install new dependencies (Vite)
npm install @tailwindcss/vite

# Or keep PostCSS
npm install @tailwindcss/postcss

Step 4: Review the Changes

git diff

Check for any issues the tool could not resolve automatically. Common things to look for:

Step 5: Test Thoroughly

# Run your build
npm run build

# Check for visual regressions
# Test responsive breakpoints
# Verify dark mode
# Check any container query usage

Key Class Name Changes

/* Renamed utilities in v4 */
bg-opacity-50    →  bg-black/50       (opacity modifier syntax)
!flex            →  flex!             (important modifier moved to end)
bg-gradient-to-t →  bg-linear-to-t   (gradient naming)
decoration-clone →  box-decoration-clone
decoration-slice →  box-decoration-slice
flex-grow        →  grow
flex-shrink      →  shrink
outline-none     →  outline-hidden    (outline-none now sets outline-style: none)
ring             →  ring-3            (default ring width changed from 3px to 1px)

Framework Integration

Next.js

# Install
npm install tailwindcss @tailwindcss/postcss
// postcss.config.mjs
const config = {
    plugins: {
        "@tailwindcss/postcss": {},
    },
};
export default config;
/* app/globals.css */
@import "tailwindcss";

Next.js uses PostCSS internally, so the PostCSS plugin is the correct integration path.

Vite (React, Vue, Svelte)

npm install tailwindcss @tailwindcss/vite
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

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

Remix

Remix uses Vite, so install the Vite plugin as shown above. If using the classic Remix compiler, use the PostCSS plugin instead.

Astro

npm install tailwindcss @tailwindcss/vite
// astro.config.mjs
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";

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

For Astro, remove the old @astrojs/tailwind integration and use the Vite plugin directly.

Best Practices for Tailwind CSS v4

  1. Embrace CSS-first configuration. Define your design tokens in @theme rather than keeping a JavaScript config file. This makes your theme portable, inspectable in browser DevTools, and overridable at runtime.
  2. Use the Vite plugin when possible. It provides the fastest builds, tightest integration, and simplest setup. Only fall back to PostCSS when your framework requires it.
  3. Leverage container queries for reusable components. Instead of using viewport breakpoints for card layouts and sidebars, use @container so components adapt to their available space.
  4. Use @utility for custom utilities. This ensures your custom classes support variants like hover:, dark:, and responsive prefixes, which would not work if defined in a regular @layer.
  5. Keep @apply usage minimal. The Tailwind team discourages heavy @apply usage. Prefer component extraction in your framework (React components, Vue SFCs) over CSS abstraction with @apply.
  6. Use oklch or oklab colors. These perceptually uniform color spaces make it easier to build accessible, consistent color palettes with predictable lightness steps.
  7. Remove autoprefixer. Lightning CSS handles vendor prefixing automatically. Keeping autoprefixer adds unnecessary build time.
  8. Use the @source directive sparingly. If automatic content detection misses files, adding a @source path is fine, but do not over-specify. Let the default detection handle the common case.

Common Mistakes and Gotchas

1. Using @layer utilities Instead of @utility

/* WRONG in v4 */
@layer utilities {
    .text-balance {
        text-wrap: balance;
    }
}

/* CORRECT in v4 */
@utility text-balance {
    text-wrap: balance;
}

Custom utilities defined in @layer utilities will not respond to variants like hover:text-balance in v4. Use the @utility directive instead.

2. Expecting Old Class Names to Work

Several classes were renamed. If you see missing styles after migration, check for these changes:

/* These v3 classes no longer exist in v4 */
bg-opacity-*     →  use color/opacity syntax (bg-blue-500/50)
text-opacity-*   →  use color/opacity syntax (text-white/80)
flex-grow-0      →  grow-0
flex-shrink       →  shrink

3. Forgetting Browser Support Requirements

Tailwind CSS v4 requires Safari 16.4+, Chrome 111+, and Firefox 128+. If your analytics show significant traffic from older browsers, do not upgrade yet. The cascade layers and @property rules that v4 depends on will not work in older browsers and cannot be polyfilled.

4. Keeping autoprefixer in the Build Pipeline

Lightning CSS already handles vendor prefixes. Running autoprefixer on top of it is redundant and can occasionally produce duplicate or conflicting prefixes.

5. Not Updating the Ring Default

In v3, ring produced a 3px ring. In v4, ring produces a 1px ring. If your design relies on the old default, change ring to ring-3 explicitly.

6. Dynamic Class Names in Templates

The upgrade tool cannot detect class names that are constructed at runtime. These need manual updating:

// The upgrade tool CANNOT fix this:
const bgClass = `bg-${color}-500`;

// Prefer using a safelist or complete class names:
const bgMap = {
    red: "bg-red-500",
    blue: "bg-blue-500",
    green: "bg-green-500",
};

What is New in v4.1+

Since the v4.0 release, the Tailwind team has shipped incremental updates. Notable additions include @starting-style support for entry animations, @custom-variant improvements, better TypeScript plugin authoring support, and additional color manipulation utilities via color-mix(). Keep an eye on the Tailwind CSS changelog for the latest updates.

Need to look up a utility? Use our Tailwind CSS Lookup tool to quickly search for any Tailwind class name, or minify your final CSS with the CSS Minifier.

Frequently Asked Questions

What is the biggest change in Tailwind CSS v4 compared to v3?

The biggest change is the shift from JavaScript-based configuration (tailwind.config.js) to CSS-first configuration using the @theme directive. In v4, you define all your design tokens directly in your CSS file alongside @import "tailwindcss". The new engine also uses Lightning CSS under the hood, delivering full builds up to 5x faster and incremental builds over 100x faster.

Do I need to manually list all my content files in Tailwind CSS v4?

No. Tailwind CSS v4 introduces automatic content detection. The framework scans your project for template files without requiring a content array, respecting your .gitignore to skip node_modules and other ignored directories. Use the @source directive only if automatic detection misses files in unusual locations.

How do I migrate from Tailwind CSS v3 to v4?

Run npx @tailwindcss/upgrade in your project root. The tool updates dependencies, migrates your JavaScript config to CSS @theme directives, converts @tailwind directives to @import "tailwindcss", and updates class names in your templates. For complex projects, you may need some manual adjustments. Node.js 20 or higher is required.

Does Tailwind CSS v4 still require PostCSS?

No. Tailwind CSS v4 uses Lightning CSS directly for its core processing, handling @import inlining, vendor prefixing, and CSS nesting without PostCSS. However, a PostCSS plugin (@tailwindcss/postcss) is available for projects that need it. For Vite projects, the @tailwindcss/vite plugin is recommended for best performance.

What browsers does Tailwind CSS v4 support?

Tailwind CSS v4 targets Safari 16.4+, Chrome 111+, and Firefox 128+. It uses modern CSS features like cascade layers, @property, and color-mix() that require these minimum versions. If you need to support older browsers, stay on Tailwind CSS v3 until your browser requirements align.

Can I still use a JavaScript config file with Tailwind CSS v4?

Yes. Add @config "./tailwind.config.js" after your @import "tailwindcss" statement to load a JavaScript or TypeScript config. This is useful for gradual migration or projects with complex plugin setups. The long-term recommendation is to migrate to @theme in CSS.

Related Resources

Tailwind CSS Cheat Sheet
Quick reference for all Tailwind utility classes
CSS Grid Complete Guide
Master two-dimensional layouts with CSS Grid
CSS Variables Complete Guide
Custom properties for dynamic theming and design tokens
Tailwind CSS Lookup Tool
Search and find any Tailwind utility class
CSS Minifier
Compress CSS for production deployment
CSS Flexbox Complete Guide
One-dimensional layout with Flexbox