Skip to main content
CodeAlchemy
Tailwind

Tailwind v3 vs v4: what changed in utility classes

CodeAlchemy Team··7 min read

Source: Tailwind CSS blog + upgrade guide

v4 is not a light reskin: the team rebuilt the engine and moved customization toward CSS-native patterns while keeping the utility mental model. If you have been on v3.x since 2022, the move feels less like a routine bump and more like opting into a new authoring environment that happens to ship the same class names you already know.

Tailwind CSS v4.0 is an all-new version of the framework optimized for performance and flexibility, with a reimagined configuration and customization experience.

TL;DR

  • No more JS config for most projects — @theme blocks in CSS replace large parts of tailwind.config.js.
  • No more `content` array — Tailwind walks your project automatically and respects .gitignore; you only reach for @source when you import classes from a sibling package or a build artifact.
  • Faster — full builds and incremental passes are an order of magnitude quicker on the published benchmarks.
  • OKLCH by default — the palette ships in a wide-gamut color space, and gradients can interpolate in it.
  • Container queries are core — no plugin install.
  • 3D transforms, masks, conic gradients, `@starting-style` — utilities you previously needed plugins or hand-rolled CSS for.

Why a rewrite happened

Tailwind Labs wrote v3 against a single assumption: PostCSS plus a JavaScript-heavy compiler is fast enough for the long tail of projects. By late 2024 that assumption was creaking — giant monorepos, design systems with thousands of tokens, and JIT scans hitting node_modules made cold builds painful. v4 introduces a new engine (often called "Oxide" in early posts) with a tight Rust core for hot paths and Lightning CSS for parsing, vendor prefixing, and bundling @import. The mental model — utility classes generated from a theme — is unchanged. What shifted is where customization lives and how the compiler finds your classes.

Configuration: from JS to CSS

In v3 a typical project ended up with a tailwind.config.js that wired plugins, declared colors, defined breakpoints, and sometimes pulled type definitions from elsewhere. v4 lets that file shrink dramatically — or disappear:

@import "tailwindcss";

@theme {
  --color-brand-500: oklch(0.70 0.22 142);
  --font-display: "Inter Display", ui-sans-serif, system-ui;
  --breakpoint-3xl: 120rem;
  --spacing: 0.25rem;
}

A few things to notice:

  • The theme is a CSS variable namespace (--color-*, --font-*, --breakpoint-*, --spacing, …). Each namespace seeds a category of utilities.
  • You can still keep a tailwind.config.js for plugins and escape hatches, but a lot of projects no longer need it day-to-day.
  • Because the theme is a real CSS file, runtime tools (devtools, Storybook, Figma plugins) can read variable names directly without bouncing through Node.

Content paths: gone unless you opt in

v3 made you maintain a content array; forget a folder and your component would render unstyled. v4 walks the project automatically, skipping node_modules, file types it cannot scan, and anything ignored by Git.

Three new at-rules give you control when automation is not enough:

  • `@source "../packages/ui/dist/.js"`** — pull classes out of a sibling package or a build artifact.
  • `@source not "../legacy"` (added in v4.1) — exclude a folder you don't want scanned.
  • `@source inline("hidden md:flex")` — safelist classes that only ever exist as runtime strings.

Most apps go from a careful content-path checklist to "it just works".

Performance, for real

Tailwind Labs published numbers that line up with what teams report in practice:

  • Full builds 3–5× faster on representative apps.
  • Incremental rebuilds in the microsecond range when no new classes are emitted — the compiler skips work entirely if your edit didn't introduce a new utility.
  • Lower memory ceiling, which matters in CI and on cheap dev machines.

If your DX pain in v3 was "save a file, wait half a second", that pain mostly evaporates.

Color, gradients, and modern CSS

The default palette switches from sRGB hex values to OKLCH, a perceptually uniform space that takes advantage of P3-capable displays. Two consequences:

  • The same step on the lightness scale (-500 vs -600) feels visually equivalent across hues. In v3 some hues looked muddy at the same step.
  • Gradient utilities can interpolate in OKLCH to avoid the gray middle that linear sRGB blends produce.

v4 expands gradient utilities to include conic and radial helpers (bg-conic-*, bg-radial-*) and linear directional ones (bg-linear-to-r, bg-linear-to-br). 3D transform utilities (rotate-x-*, translate-z-*, perspective-*) ship in core for card flips and depth tricks that previously required hand-rolled CSS.

Variants you didn't have

  • `starting:` — pairs with @starting-style for popovers and discrete property transitions, finally usable without a polyfill.
  • `not-*` and `in-*` — express "not in :hover" and "inside an open details", composing with the rest of the variant system.
  • `details-content:` — style only the content of a <details> element after the marker.

Plugins folded into core

If you carried these in v3, drop them on upgrade:

  • @tailwindcss/container-queries@container and the @-prefixed variants live in core.
  • @tailwindcss/aspect-ratio — modern aspect-* utilities already shipped; v4 finalizes the move.

Plugins still exist (forms, typography), but the surface that needed them is smaller.

Migration: what actually breaks

The official upgrade guide ships an automated tool (npx @tailwindcss/upgrade) that handles most of the mechanical edits:

  • Rewrites @tailwind base/components/utilities to @import "tailwindcss".
  • Moves theme overrides from JS to a @theme block.
  • Collapses arbitrary values that can now be expressed as dynamic utilities.
  • Flags class renames (a few, mostly around opacity-as-modifier syntax).

What the tool can't do for you:

  • Decide whether to keep a tailwind.config.js for plugins or fully migrate.
  • Audit custom plugins that hooked into the v3 internals — those plugin APIs changed.
  • Re-test visual regressions if your design tokens were tuned against the old sRGB palette and now interpolate differently.

A reasonable order: bump on a branch, run the upgrade tool, take screenshots of representative pages, diff colors and gradients, then deal with plugin-specific edge cases.

When *not* to upgrade today

  • You're on a frozen LTS line because of a contractual lock — wait for v4.x to settle (v4.2 already covers webpack and logical properties; v4.1 added text shadows and masks).
  • You ship a UI library that has to support consumers on v3 and v4 simultaneously — the engine differences make a single class output non-trivial; document a peer range and let downstream pick.
  • Your toolchain depends on a v3 plugin that hasn't released a v4-compatible build yet.

For everything else, the cost is "an afternoon for a normal repo, two days for a complex monorepo", and the win is durable: faster builds, less config, and better defaults.

Further reading

Source / further reading: Tailwind CSS blog + upgrade guide