Web DevelopmentTailwind CSSCSSFrontendMigration

Tailwind CSS v4 vs v3: What Actually Changed and Why It Matters

Breaking down the new CSS-first engine, the PostCSS plugin split, and the real migration gotchas from v3 to v4.

March 31, 2026

tailwind css v4 vs v3

Tailwind v4 is a ground-up rewrite. The utility class names are mostly the same, the design philosophy is the same, but the internals — how it processes CSS, how you configure it, how it integrates with your build — changed significantly. If you're upgrading from v3, the familiar surface hides some real breaking changes.

The Biggest Change: CSS-First Configuration

In v3, configuration lived in tailwind.config.js. Custom colors, fonts, breakpoints, plugins — all JavaScript.

In v4, configuration is CSS-first. You configure Tailwind by writing CSS:

/* v4 — in your main CSS file */
@import "tailwindcss";

@theme {
  --color-brand: #0ea5e9;
  --color-brand-dark: #0284c7;
  --font-sans: "Inter", sans-serif;
  --breakpoint-3xl: 1920px;
}

These CSS variables become Tailwind utilities automatically. --color-brand becomes bg-brand, text-brand, border-brand. --breakpoint-3xl becomes the 3xl: variant.

The tailwind.config.js file still works for backwards compatibility, but the CSS-first approach is the intended path forward.

The PostCSS Plugin Split

In v3, tailwindcss was both the core library and the PostCSS plugin. One package handled everything.

In v4, these are separate packages:

# v3
pnpm add -D tailwindcss postcss autoprefixer

# v4
pnpm add -D tailwindcss @tailwindcss/postcss

Your postcss.config.mjs:

// v3
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

// v4
export default {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}

Note that autoprefixer is no longer needed — v4 handles vendor prefixing internally.

The New @import Syntax

In v3, you used @tailwind directives:

/* v3 */
@tailwind base;
@tailwind components;
@tailwind utilities;

In v4, it's a single import:

/* v4 */
@import "tailwindcss";

This also means you can import Tailwind as part of a standard CSS cascade, which makes it easier to control ordering with your own reset or base styles.

Content Detection: No More content Config

v3 required you to list every file path that might contain Tailwind classes:

// v3 tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
}

v4 uses automatic content detection. It scans your project for files containing utility classes without any configuration. For most projects, this just works. Edge cases (e.g., classes in JSON files, dynamically constructed class strings) may still need manual configuration via @source directives:

@import "tailwindcss";
@source "../node_modules/my-ui-library"; /* explicit extra source */

What Didn't Change

Most utility classes are identical. flex, grid, p-4, text-lg, hover:bg-blue-500 — if it worked in v3 it works in v4.

The variants system (hover:, focus:, dark:, responsive prefixes) works the same way.

The @apply directive still works in v4 for applying utilities in component classes:

.btn-primary {
  @apply bg-brand text-white px-4 py-2 rounded-lg;
}

Migration Gotchas

ring utilities changed. ring used to add a 3px ring by default. In v4 it defaults to 1px. If you relied on ring for focus styles, check your designs.

Opacity modifiers tightened. bg-blue-500/50 still works, but some edge cases with the old opacity plugin behavior changed.

theme() function. If you used theme('colors.blue.500') in custom CSS, you'll need to migrate to CSS variables: var(--color-blue-500).

JIT is the only mode. v3 had both JIT (default) and classic mode. v4 is JIT-only. If you had any workarounds for non-JIT behavior, remove them.

Should You Upgrade Now?

For greenfield projects: yes. The CSS-first config is cleaner, automatic content detection removes maintenance overhead, and the performance is faster.

For existing v3 projects: the migration is mechanical but requires attention. The automatic upgrade tool (npx @tailwindcss/upgrade) handles most of it, but review the ring and opacity changes manually.

If your project has a lot of custom plugins written for v3 the JavaScript API, check plugin compatibility before upgrading — the plugin API changed.

Key Takeaways

  • Configuration moved from tailwind.config.js to CSS @theme variables — leaner and more composable
  • @tailwindcss/postcss replaces tailwindcss as the PostCSS plugin; autoprefixer is no longer needed
  • @import "tailwindcss" replaces the three @tailwind directives
  • Content detection is automatic in v4; no more content array in config for standard setups
  • Most utility classes are identical; the gotchas are ring defaults, opacity edge cases, and the theme() function