Type something to search...
What's New in Tailwind CSS v4?

What's New in Tailwind CSS v4?

Tailwind CSS v4 is the biggest rewrite the framework has had since it launched. On the surface you still write flex, pt-4, and text-center in your markup, but almost everything underneath has changed: the engine, the way you configure it, how it finds your class names, and which CSS features it relies on. If you've been putting off the upgrade, or you're starting a new project and want to know what you're getting, this guide walks through the changes that actually affect your day-to-day work.

I'll cover the new CSS-first configuration, the faster engine, automatic content detection, the modern CSS features v4 builds on, the new utilities and variants, and the breaking changes that tend to trip people up during migration.

The Big Idea: Configuration Lives in CSS Now

In v3, a Tailwind project revolved around tailwind.config.js. You extended the theme there, registered plugins there, and listed your content paths there. In v4, the default setup has no JavaScript config file at all. You import Tailwind from your stylesheet and customize it with CSS.

The minimal setup is one line:

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

That single import replaces the old trio of @tailwind base;, @tailwind components;, and @tailwind utilities;. It pulls in Preflight (the reset), the default theme, and every utility.

Customizing the Theme with @theme

Design tokens are now defined with the @theme directive. Each variable you declare there does two things: it becomes a real CSS custom property on :root, and it generates the matching utilities.

@import "tailwindcss";

@theme {
  --color-brand-50: oklch(0.97 0.02 250);
  --color-brand-500: oklch(0.62 0.19 250);
  --color-brand-900: oklch(0.32 0.1 250);

  --font-display: "Satoshi", ui-sans-serif, system-ui, sans-serif;

  --breakpoint-3xl: 1920px;

  --radius-card: 1.25rem;
}

With that in place you can immediately use bg-brand-500, text-brand-900, font-display, 3xl:grid-cols-6, and rounded-card in your HTML. The naming convention is the key: the variable namespace (--color-*, --font-*, --breakpoint-*, --radius-*, --shadow-*, and so on) decides which utilities get generated.

Because the tokens are real custom properties, you can use them anywhere, including in plain CSS or inline styles:

.marketing-hero {
  background: linear-gradient(
    to bottom right,
    var(--color-brand-50),
    var(--color-brand-500)
  );
  border-radius: var(--radius-card);
}

This was awkward in v3, where you had to reach for the theme() function or duplicate values. In v4, the theme and your runtime CSS share the same source of truth.

Replacing the Default Palette

If you want to wipe out a whole namespace rather than extend it, set it to initial first:

@theme {
  --color-*: initial;

  --color-ink: #0f172a;
  --color-paper: #f8fafc;
  --color-accent: #0ea5e9;
}

Now only ink, paper, and accent exist as color utilities. Setting --*: initial clears the entire default theme if you want to start from nothing.

Can I Still Use a JavaScript Config?

Yes. For gradual migrations, the @config directive loads a legacy file:

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

Some options from v3, such as corePlugins, safelist, and separator, aren't supported this way, so treat @config as a bridge rather than a permanent home.

A Faster Engine

v4 ships with a rewritten engine, with performance-critical parts written in Rust. The Tailwind team's own benchmarks showed full builds several times faster than v3 and incremental rebuilds, the ones that happen every time you save a file, dramatically faster, often finishing in microseconds when no new classes are introduced.

In practice, the difference is most noticeable on large projects where v3's watcher had started to lag. You don't configure anything to get this; it's just the new baseline.

Tailwind v4 also bundles what used to require extra tooling. Vendor prefixing, nesting, and @import handling are built in, powered by Lightning CSS, so you can drop autoprefixer and postcss-import from most setups.

Installing v4

There are three first-party integrations. Pick the one that matches your build tool.

Vite

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

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

PostCSS (Next.js and most other frameworks)

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

The Standalone CLI

npm install tailwindcss @tailwindcss/cli
npx @tailwindcss/cli -i ./src/app.css -o ./dist/app.css --watch

Notice that the PostCSS plugin and CLI are now separate packages. The old tailwindcss PostCSS plugin entry no longer works, which is one of the first errors people hit after upgrading.

Automatic Content Detection

You no longer have to tell Tailwind where your templates live. v4 scans your project automatically and skips anything listed in .gitignore, along with binary files like images and videos. For most projects, the content array from v3 simply disappears.

When you do need control, use @source:

@import "tailwindcss";

/* Scan a package that lives in node_modules */
@source "../node_modules/@acme/ui-kit";

/* Exclude a folder that would otherwise be scanned */
@source not "../src/legacy";

There's also an inline form for safelisting classes that never appear literally in your source, such as classes built from CMS data:

@source inline("bg-red-500 bg-green-500 bg-amber-500");

The not and inline() forms arrived in v4.1, so make sure you're on a current release if you rely on them.

The old rule still applies: Tailwind finds classes by scanning text, so it can't see class names you assemble at runtime. Write "bg-red-500" in full, not `bg-${color}-500`.

Built on Modern CSS

v4 targets modern browsers and uses platform features that v3 couldn't assume.

Native Cascade Layers

The generated CSS is organized into real @layer blocks: theme, base, components, and utilities. This means specificity is managed by the browser's cascade layer rules rather than by source order tricks, and your own unlayered CSS will beat Tailwind's utilities unless you put it in a layer too. If a hand-written rule suddenly overrides a utility after upgrading, that's usually why.

Registered Custom Properties

Tailwind uses @property to register typed custom properties internally. That's what makes things like animating gradient stops possible, and it's why utilities like shadow-*, ring-*, and transforms compose cleanly without the long chains of --tw-* variables stomping on each other.

color-mix() and OKLCH

The default palette was redefined in OKLCH, which gives more vivid colors on wide-gamut displays and more even steps between shades. Opacity modifiers like bg-sky-500/40 are now implemented with color-mix(), so they work with any color, including ones defined as CSS variables or currentColor.

Container Queries, Built In

Container queries used to need a plugin. Now they're core:

<div class="@container">
  <article class="grid grid-cols-1 gap-4 @md:grid-cols-2 @3xl:grid-cols-3">
    <!-- cards -->
  </article>
</div>

@md: responds to the width of the nearest @container ancestor instead of the viewport. There are max-width variants too, such as @max-md:, and you can combine them for ranges.

New Utilities and Variants

A lot of small additions make everyday work nicer.

Dynamic Values Without Configuration

Spacing is now driven by a single --spacing variable, and many utilities accept any number. mt-17, w-29, grid-cols-15, and z-60 just work without extending the theme or using arbitrary-value brackets.

@theme {
  --spacing: 0.25rem; /* the default: mt-4 = calc(var(--spacing) * 4) */
}

Gradients Get an Upgrade

bg-gradient-to-r is now bg-linear-to-r, and linear gradients accept angles such as bg-linear-45. Radial and conic gradients are first-class:

<div class="h-40 rounded-xl bg-linear-45 from-sky-400 to-indigo-600"></div>
<div
  class="size-40 rounded-full bg-conic from-pink-500 via-amber-400 to-pink-500"
></div>
<div class="h-40 bg-radial-[at_25%_25%] from-white to-slate-900"></div>

You can also choose the interpolation color space, for example bg-linear-to-r/oklch, which avoids the muddy middle you often get when blending in sRGB.

3D Transforms

New utilities cover rotate-x-*, rotate-y-*, perspective-*, transform-3d, and backface-hidden, so card-flip effects no longer need custom CSS.

Entry Animations with starting:

The starting: variant maps to CSS @starting-style, which lets an element transition in from a starting state when it first appears, for example when a popover opens:

<div
  popover
  id="menu"
  class="transition-discrete opacity-100 transition-opacity duration-300 starting:open:opacity-0"
>
  Menu contents
</div>

Browser support for @starting-style is newer than the rest of v4's baseline, so treat it as progressive enhancement: browsers without it simply show the element without the fade.

More Variants

  • not-* negates another variant, for example not-hover:opacity-75 or not-first:border-t.
  • in-* works like group-* without needing a group class on the parent.
  • nth-* variants such as nth-3: and nth-last-2:.
  • inert: targets elements with the inert attribute.
  • **: targets all descendants, extending the *: direct-children variant.
  • inset-shadow-* and inset-ring-* let you stack inner shadows alongside regular ones.
  • field-sizing-content makes a textarea grow with its content in supporting browsers.

v4.1 added text-shadow-*, mask-* utilities for image and gradient masks, pointer-fine: and pointer-coarse: variants, and wrap-break-word.

Writing Your Own Utilities and Variants

The v3 pattern of @layer utilities with custom classes has been replaced by @utility, which registers a class as a real utility so it works with variants:

@utility content-auto {
  content-visibility: auto;
}

@utility scrollbar-hidden {
  scrollbar-width: none;
  &::-webkit-scrollbar {
    display: none;
  }
}

Now hover:content-auto and lg:scrollbar-hidden work as you'd expect.

Custom variants use @custom-variant. The most common one is class-based dark mode, since v4 defaults to the prefers-color-scheme media query:

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

Or a data attribute, if that's how your theme switcher works:

@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));

JavaScript plugins still work, loaded from CSS with @plugin:

@plugin "@tailwindcss/typography";

Breaking Changes to Watch For

Most of the migration pain comes from renamed or re-scaled utilities that still exist, so nothing errors, but your design looks slightly off. These are the ones worth checking.

Renamed Scales

v3v4
shadow-smshadow-xs
shadowshadow-sm
rounded-smrounded-xs
roundedrounded-sm
blur-smblur-xs
outline-noneoutline-hidden
ringring-3

The pattern: the bare, unsuffixed version of each utility shifted one step down, and -sm became -xs. A class like shadow without a size still exists but now means something smaller than before.

Changed Defaults

  • Border color now defaults to currentColor instead of gray-200. A bare border class will be as dark as your text. Add a color, or restore the old default in your base layer.
  • Ring width defaults to 1px instead of 3px, and the default ring color is currentColor.
  • Placeholder text uses the current text color at 50% opacity rather than a fixed gray.
  • Buttons get cursor: default, matching browser defaults, instead of cursor: pointer.

If you want the v3 border behavior back globally:

@layer base {
  *,
  ::after,
  ::before,
  ::backdrop,
  ::file-selector-button {
    border-color: var(--color-gray-200, currentColor);
  }
}

Removed Utilities

The deprecated opacity utilities are gone. Replace bg-black bg-opacity-50 with bg-black/50, and do the same for text-opacity-*, border-opacity-*, and the rest. flex-shrink-* and flex-grow-* are now shrink-* and grow-*, and overflow-ellipsis is text-ellipsis.

Syntax Changes

  • Important modifier goes at the end: bg-red-500! rather than !bg-red-500. The old form still works but is deprecated.
  • CSS variables in arbitrary values use parentheses: bg-(--brand-color) instead of bg-[--brand-color].
  • Stacked variants apply left to right, so v3's first:*:pt-0 becomes *:first:pt-0.
  • space-x-* and space-y-* use a different selector internally, which can change results in layouts with inline elements. Switching to gap-* on a flex or grid parent avoids the issue entirely.

Browser Requirements

v4 targets Safari 16.4 and newer, Chrome 111 and newer, and Firefox 128 and newer. It relies on @property, color-mix(), and cascade layers, which older browsers lack. If you have to support older browsers, staying on v3.4 is the officially recommended route.

How to Upgrade

The Tailwind team ships an automated upgrade tool. Run it on a clean branch:

npx @tailwindcss/upgrade

It needs Node.js 20 or newer. The tool updates dependencies, migrates your JavaScript config into @theme, rewrites your CSS entry file, and renames utilities in your templates. It handles the bulk of the mechanical work well.

After it finishes:

  1. Review the diff carefully, especially any class names the tool changed inside dynamic strings.
  2. Look for bare border and ring classes and decide whether they need an explicit color.
  3. Check custom CSS that used to sit in @layer components or @layer utilities and move reusable pieces to @utility.
  4. Test your dark mode, since the default switched to the media query strategy.
  5. Click through the app visually. Shadows and radii shifting by one step are easy to miss in a code review but obvious on screen.

Should You Upgrade?

For new projects, yes, start on v4. The setup is simpler, the build is faster, and the CSS-first theme makes design tokens far easier to share with non-Tailwind code.

For existing projects, the decision mostly comes down to browser support. If your audience is on reasonably current browsers, the upgrade tool gets you most of the way, and the remaining work is a visual QA pass. If you have contractual support for older Safari versions, stay on v3.4 until those requirements change.

Conclusion

Tailwind CSS v4 keeps the utility-first workflow you already know and rebuilds everything around it. Configuration moves into CSS with @theme, content detection is automatic, the engine is much faster, and the framework now leans on native cascade layers, @property, color-mix(), and container queries instead of working around their absence.

The breaking changes are real but manageable: a handful of renamed scales, a few changed defaults, and some syntax tweaks. Run the upgrade tool, review the diff, do a careful visual pass, and you'll end up with a simpler setup that's closer to plain CSS than Tailwind has ever been.

Tags :
Share :

Related Posts

A Complete Guide to CSS Container Queries

A Complete Guide to CSS Container Queries

For more than a decade, responsive design meant one thing: media queries. You asked the browser how wide the viewport was and adjusted your layout ac

Continue Reading
A Comprehensive Guide to Installing Next.js

A Comprehensive Guide to Installing Next.js

Next.js has emerged as a powerful framework for building React applications, offering features like server-side rendering, static site generation, an

Continue Reading
Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

Advanced CSS with clamp(), min(), and max(): Simplifying Dynamic Styling

CSS has evolved significantly, and modern tools like clamp(), min(), and max() are powerful game-changers in dynamic styling. If you’ve struggl

Continue Reading