Tailwind CSS v4.0 shipped in January 2025 and it is not a minor release. The JavaScript config file is gone, the CSS entry point changed, the performance ceiling moved by an order of magnitude, and a handful of utility names were quietly renamed in ways that will silently break existing markup. If you have been putting off the upgrade, or you are starting a new project and want to understand why things look different from every tutorial written before 2025, this is the reference to bookmark.
This cheatsheet covers the things that actually matter day-to-day: how to set up a new project, how the CSS-first configuration system works, what new utilities shipped, how container queries now work without a plugin, and the exact class renames you need to know when migrating from v3.
Setup: one import, no config file
In v3 you had three directives and a tailwind.config.js sitting at the project root. In v4 that collapses to a single CSS import:
/* v3 */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* v4 */
@import "tailwindcss";
Content detection is now automatic. Tailwind walks your project using the same heuristics as .gitignore and finds template files without a content array. If you have templates in a location that would be ignored (say, inside node_modules), you can opt those in explicitly:
@import "tailwindcss";
@source "../node_modules/@my-company/ui-lib";
Vite plugin vs. PostCSS
The PostCSS plugin is now @tailwindcss/postcss. There is also a first-class Vite plugin — @tailwindcss/vite — that integrates more tightly with Vite's pipeline and gives better incremental performance. If your project uses Vite (Astro, SvelteKit, most modern setups do), prefer the Vite plugin:
// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss()],
});
The Vite plugin also handles @import resolution internally, so you no longer need postcss-import as a separate dependency.
CSS-first configuration with @theme
This is the biggest conceptual shift. All theme customization now happens inside a @theme block in your CSS file. You define CSS custom properties there, and Tailwind automatically generates the corresponding utility classes.
@import "tailwindcss";
@theme {
--font-display: "Satoshi", sans-serif;
--breakpoint-3xl: 1920px;
--color-brand-500: oklch(0.62 0.19 250);
--color-brand-600: oklch(0.54 0.19 250);
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
--spacing-18: 4.5rem;
}
Every variable defined in @theme is also exposed at runtime as a CSS custom property on :root. That means your JavaScript can read getComputedStyle(document.documentElement).getPropertyValue('--color-brand-500') and get the real value — no more JS-side theme imports.
The naming convention is what drives class generation. --color-brand-500 creates bg-brand-500, text-brand-500, border-brand-500, and so on. --breakpoint-3xl creates a 3xl: responsive variant. --spacing-18 creates mt-18, w-18, p-18, etc.
Keeping a JavaScript config
If you have an existing tailwind.config.js you are not ready to migrate, you can reference it with the @config directive. Note that in v4 JavaScript configs are no longer auto-detected — you have to opt in explicitly:
@import "tailwindcss";
@config "../../tailwind.config.js";
The corePlugins option has been removed entirely in v4, so any config that uses it will need to be cleaned up before this works cleanly.
The Oxide engine
The internal engine was rewritten in Rust and renamed Oxide. The Tailwind team wrote their own CSS parser rather than relying on PostCSS, which allowed them to optimize data structures specifically for Tailwind's scanning patterns.
The measured numbers from the official release: full builds go from 378ms (v3.4) to 100ms (v4.0) — about 3.8x faster. Incremental builds that produce new CSS go from 44ms to 5ms. The standout number is incremental builds that produce no new CSS: from 35ms down to 192 microseconds, a 182x improvement. For a large project with a hot module reload loop, that last number is the one you feel.
As a side effect, CSS output size dropped 15–25% compared to v3 for equivalent styling, because v4 uses native cascade layers (@layer theme, base, components, utilities) which let the browser handle specificity correctly without the selector gymnastics v3 required.
New utilities worth knowing
3D transforms
First-class 3D transform utilities are now included. Use perspective-* on a parent, then rotate-x-*, rotate-y-*, rotate-z-*, translate-z-*, and transform-3d on children:
<div class="perspective-500">
<div class="transform-3d rotate-x-12 rotate-y-6 transition-transform hover:rotate-x-0">
Card content
</div>
</div>
Gradients
bg-gradient-* was renamed to bg-linear-*. Radial and conic gradients are now built-in utilities — bg-radial-* and bg-conic-*. Linear gradients now accept angle utilities directly: bg-linear-45 produces a 45-degree gradient. Color interpolation mode modifiers let you control how gradient midpoints are calculated: bg-linear-to-r/oklch interpolates in OKLCH color space, which produces perceptually uniform gradients through non-grey midpoints.
Shadow and ring stacking
New inset-shadow-* and inset-ring-* utilities let you layer up to four shadow effects. This replaces the common workaround of combining box-shadow and outline with ring.
Miscellaneous
- Text shadow:
text-shadow-sm,text-shadow-md,text-shadow-lg,text-shadow-none - Masking:
mask-*utilities for CSS masking - field-sizing:
field-sizing-contentmakes textareas auto-resize to fit their content — no JavaScript needed - font-stretch:
font-stretch-*for variable font width axes - color-scheme:
color-scheme-light/color-scheme-darkcontrols scrollbar and form control rendering - Dynamic values: Any number works for grid columns, spacing, etc. —
grid-cols-15just works without config
New variants
starting:— applies@starting-stylefor entry animations (starting:opacity-0 starting:translate-y-2)not-*— logical negation (not-hover:opacity-75,not-focus:ring-0)nth-*— CSS:nth-child()pseudo-class (nth-2:bg-gray-50)in-*— ancestor-based styling without adding agroupclass to the parentinert:— style elements marked as inertopen:— works on native<details>,<dialog>, and Popover API elements
Container queries (built-in, no plugin)
The @tailwindcss/container-queries plugin is gone. Container query support is now in core. Mark an element as a container, then use size variants prefixed with @ on its children:
<div class="@container">
<div class="grid grid-cols-1 @sm:grid-cols-2 @lg:grid-cols-3 @xl:grid-cols-4">
<!-- cards -->
</div>
</div>
Range queries are supported with @min-* and @max-*:
<!-- only applies between sm and lg container sizes -->
<div class="@min-sm:@max-lg:grid-cols-2"></div>
You can also name containers for nested targeting:
<div class="@container/sidebar">
<nav class="@sm/sidebar:flex-col"></nav>
</div>
v3 → v4 migration reference
The automated upgrade tool handles most of this: npx @tailwindcss/upgrade. But it is worth knowing what changed so you can read diffs and catch edge cases.
Renamed utilities (the ones most likely to cause silent visual regressions)
shadow-sm→shadow-xsshadow(bare) →shadow-smrounded-sm→rounded-xsrounded(bare) →rounded-smblur-sm→blur-xsblur(bare) →blur-smoutline-none→outline-hidden(a newoutline-nonenow literally meansoutline: none)ring(bare, was 3px) →ring-3
Removed utilities — use the modern equivalents
bg-opacity-*,text-opacity-*,border-opacity-*→ use opacity modifiers:bg-black/50flex-shrink-*→shrink-*flex-grow-*→grow-*overflow-ellipsis→text-ellipsisdecoration-slice→box-decoration-slice
Syntax changes
The important modifier moved from a prefix to a suffix:
<!-- v3 -->
<div class="!bg-red-500 hover:!bg-red-600"></div>
<!-- v4 -->
<div class="bg-red-500! hover:bg-red-600!"></div>
Arbitrary CSS variable values use parentheses instead of brackets:
<!-- v3 -->
<div class="bg-[--brand-color]"></div>
<!-- v4 -->
<div class="bg-(--brand-color)"></div>
Variant stacking order reverses when using the child variant (*:):
<!-- v3 -->
<ul class="first:*:pt-0"></ul>
<!-- v4 -->
<ul class="*:first:pt-0"></ul>
Default value changes that can surprise you
- Border color default changed from
gray-200tocurrentColor - Ring width default changed from
3pxto1px - Ring color default changed from
blue-500tocurrentColor - Button cursor default changed from
pointertodefault— addcursor-pointerexplicitly if you want the hand cursor on buttons - Hover variants now include a
@media (hover: hover)check, which affects touch device behavior
Custom utilities and @apply
Custom utilities are no longer defined inside @layer utilities. Use the new @utility API instead:
<!-- v3 -->
@layer utilities {
.tab-4 { tab-size: 4; }
}
<!-- v4 -->
@utility tab-4 {
tab-size: 4;
}
Browser support floor
v4 requires Safari 16.4+, Chrome 111+, and Firefox 128+. If your project must support older browsers, v3 is still maintained.
Colors: OKLCH by default
The entire default color palette was regenerated in OKLCH color space. OKLCH provides perceptually uniform lightness steps and access to the P3 gamut on capable displays, meaning colors like Tailwind's default blue-500 are more vivid on modern screens. The palette was designed to be visually equivalent to v3 — the same overall feel, just richer on P3 displays. This is not a breaking change in the sense that pages will not look wildly different, but if you are doing precise color matching you may notice the shift.
Opacity modifiers now use color-mix() under the hood, which means they work correctly even on arbitrary CSS custom properties — a long-standing limitation in v3.
Summary
Tailwind v4 is a genuine improvement once you have absorbed the change in mental model. The CSS-first configuration is actually more natural than the JavaScript config once you are used to it — your design tokens live in the same file as your styles, and they automatically become both utility classes and CSS variables. The Oxide engine makes the feedback loop faster. Container queries in core remove one plugin dependency. The migration is largely mechanical — run npx @tailwindcss/upgrade, review the diff, manually check for the renamed shadow and rounded utilities since those are the most likely to cause subtle visual regressions, and update any bare ring or border classes that relied on old defaults.