Summit Themes
Blog

The Tailwind CSS v4 Cheatsheet

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-content makes 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-dark controls scrollbar and form control rendering
  • Dynamic values: Any number works for grid columns, spacing, etc. — grid-cols-15 just works without config

New variants

  • starting: — applies @starting-style for 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 a group class to the parent
  • inert: — style elements marked as inert
  • open: — 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-smshadow-xs
  • shadow (bare) → shadow-sm
  • rounded-smrounded-xs
  • rounded (bare) → rounded-sm
  • blur-smblur-xs
  • blur (bare) → blur-sm
  • outline-noneoutline-hidden (a new outline-none now literally means outline: none)
  • ring (bare, was 3px) → ring-3

Removed utilities — use the modern equivalents

  • bg-opacity-*, text-opacity-*, border-opacity-* → use opacity modifiers: bg-black/50
  • flex-shrink-*shrink-*
  • flex-grow-*grow-*
  • overflow-ellipsistext-ellipsis
  • decoration-slicebox-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-200 to currentColor
  • Ring width default changed from 3px to 1px
  • Ring color default changed from blue-500 to currentColor
  • Button cursor default changed from pointer to default — add cursor-pointer explicitly 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.