The @import Directive Replaces Directives
/* v3 globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* v4 globals.css — single import */
@import "tailwindcss";
/* Source detection is automatic in v4 — no content[] array needed */
/* But you can still customize: */
@source "./src/**/*.{ts,tsx,mdx}";
@source not "./src/**/*.test.{ts,tsx}";
New Color System — OKLCH by Default
Tailwind v4 switched to OKLCH (perceptually uniform color space) for all default colors. Colors look better across screens, and the scale is more consistent. But if you were relying on exact v3 hex values, your colors will shift slightly.
/* v4 uses OKLCH natively */
@theme {
/* Define colors in oklch for perceptual consistency */
--color-primary: oklch(55% 0.2 250);
--color-primary-light: oklch(75% 0.15 250);
--color-primary-dark: oklch(35% 0.25 250);
/* You can still use hex if migrating from v3 */
--color-legacy-brand: #0ea5e9;
}
/* Usage is identical to v3 */
/* bg-primary, text-primary-light, border-primary-dark */
/* Migrating v3 custom colors to v4 */
/* v3 */
/* colors: { primary: { DEFAULT: '#0ea5e9', dark: '#0284c7' } } */
/* v4 equivalent in @theme */
@theme {
--color-primary: #0ea5e9; /* becomes bg-primary, text-primary */
--color-primary-dark: #0284c7; /* becomes bg-primary-dark */
}
Container Queries — Built In, No Plugin
Container queries were a plugin in v3. In v4 they are first-class utilities.
<!-- v3 required @tailwindcss/container-queries plugin -->
<!-- v4: built in -->
<div class="@container">
<div class="grid grid-cols-1 @sm:grid-cols-2 @lg:grid-cols-3 gap-4">
<!-- columns change based on container width, not viewport -->
<ProductCard />
<ProductCard />
<ProductCard />
</div>
</div>
<!-- Named containers for nested scenarios -->
<div class="@container/sidebar">
<nav class="@sm/sidebar:flex-row flex-col">
<!-- ... -->
</nav>
</div>
/* Custom container breakpoints in @theme */
@theme {
--container-3xs: 16rem;
--container-2xs: 18rem;
--container-xs: 20rem;
/* sm, md, lg, xl, 2xl are built-in */
}
@starting-style — Animate Elements on Enter
One of the most requested CSS features: animate an element when it first appears in the DOM. v4 exposes this via the starting variant.
<!-- Animate a modal/dialog entering -->
<div
class="
opacity-100 translate-y-0 scale-100
transition-all duration-300 ease-out
starting:opacity-0 starting:translate-y-4 starting:scale-95
"
>
Modal content that fades/slides in
</div>
<!-- Works with popover API too -->
<div
popover
class="
opacity-100
transition-opacity duration-200
starting:opacity-0
open:opacity-100
"
>
Popover with enter animation
</div>
The not-* Variant
<!-- v3: workaround with arbitrary variants -->
<button class="[&:not(:disabled)]:hover:bg-blue-600">
<!-- v4: clean not-* variant -->
<button class="not-disabled:hover:bg-blue-600">
<!-- not-last, not-first, not-focus, etc. -->
<li class="not-last:border-b border-border">
List item with divider except last
</li>
<!-- Combine with other variants -->
<input class="not-placeholder-shown:border-brand">
<!-- border shows when input has a value -->
</input>
New Gradient Utilities
<!-- v4 gradient improvements -->
<!-- Interpolation mode (fixes gray muddy midpoints) -->
<div class="bg-linear-to-r from-purple-500 to-pink-500 bg-linear-[in_oklch]">
Vivid gradient without muddy center
</div>
<!-- Conic gradients -->
<div class="bg-conic from-red-500 via-yellow-500 to-green-500">
Color wheel
</div>
<!-- Radial gradients -->
<div class="bg-radial from-blue-400 to-blue-900">
Spotlight effect
</div>
<!-- Gradient position -->
<div class="bg-radial-[at_30%_20%] from-white to-gray-900">
Off-center radial
</div>
Migrating Plugins and Custom Utilities
/* v3 plugin (JavaScript): */
/* plugin(({ addUtilities }) => {
addUtilities({
'.text-balance': { textWrap: 'balance' },
'.text-pretty': { textWrap: 'pretty' },
})
}) */
/* v4 equivalent — just write CSS: */
@layer utilities {
.text-balance {
text-wrap: balance;
}
.text-pretty {
text-wrap: pretty;
}
}
/* Custom components */
@layer components {
.btn-primary {
@apply inline-flex items-center justify-center px-4 py-2
rounded-lg bg-brand-500 text-white font-medium
hover:bg-brand-600 focus-visible:outline-2
focus-visible:outline-offset-2 focus-visible:outline-brand-500
disabled:opacity-50 disabled:cursor-not-allowed
transition-colors duration-150;
}
}
v3 to v4 Class Name Changes
# Run the official codemod first — handles ~80% of renames
npx @tailwindcss/upgrade@next
# Manual fixes after codemod:
# shadow-sm → shadow-xs
# shadow → shadow-sm
# rounded → rounded-sm
# outline-none → outline-hidden (preserves a11y ring)
# blur → blur-sm
# ring → ring-3 (ring width changed from 3px to 1px default)
# decoration-* → underline-* (text-decoration shorthand)
/* Before: v3 arbitrary values */
/* class="p-[14px] text-[15px] leading-[1.6]" */
/* After: still valid in v4, but theme values preferred */
@theme {
--spacing-3-5: 0.875rem; /* 14px */
--text-body: 0.9375rem; /* 15px */
--leading-reading: 1.6;
}
/* class="p-3-5 text-body leading-reading" */
Dark Mode: class Strategy Still Works
/* v4 dark mode configuration */
@import "tailwindcss";
/* Class-based dark mode (recommended) */
@variant dark (&:is(.dark *));
/* Or media-query based */
/* @variant dark (@media (prefers-color-scheme: dark)); */
/* Custom variant: system with manual override */
@variant dark {
&:is([data-theme="dark"] *),
@media (prefers-color-scheme: dark) {
&:not([data-theme="light"] *)
}
}
Full Migration Checklist
# 1. Update package
npm install tailwindcss@^4.0
# 2. Run official codemod
npx @tailwindcss/upgrade@next
# 3. Remove tailwind.config.js (after migrating theme to @theme)
# 4. Update globals.css:
# - Replace @tailwind directives with @import "tailwindcss"
# - Move theme.extend into @theme block
# - Move plugins into @layer utilities/@layer components
# 5. Fix renamed classes (shadow, rounded, ring, outline-none)
# 6. Test dark mode — verify @variant dark config
# 7. Remove @tailwindcss/container-queries if installed
# (it conflicts with v4 built-in)
# 8. Check PostCSS config — v4 uses its own bundler;
# PostCSS is optional and only needed for other plugins
# 9. Run production build and check for purge issues
# (content scanning changed — @source if needed)
People Also Ask
Is Tailwind CSS v4 backward compatible with v3?
No, v4 has multiple breaking changes. The official upgrade tool (npx @tailwindcss/upgrade@next) handles about 80% of the migration automatically, but you will need to manually migrate your tailwind.config.js theme extensions to the @theme directive and fix renamed utility classes (shadow, rounded, ring). Budget 1-2 hours for a medium-sized project. The new CSS-first approach is significantly cleaner once you have migrated.
Do I still need PostCSS with Tailwind v4?
Not for Tailwind itself. v4 ships its own Rust-based engine and Vite plugin that do not require PostCSS. You only need PostCSS if you are using other PostCSS plugins (Autoprefixer, cssnano, etc.). If you are using only Tailwind, remove your postcss.config.js and use @tailwindcss/vite (for Vite/Next.js) or the standalone CLI instead.
What happened to the JIT engine in Tailwind v4?
The JIT engine from v3 is the default and only mode in v4 — there is no "classic" mode. v4 takes this further by using an even faster Rust-based scanner and bundler. Content detection is now automatic (no content: [] array required) — Tailwind scans your project files automatically. Use the @source directive only when you need to add non-standard file paths or explicitly exclude directories like test files.
Comments · 0
No comments yet. Be the first to share your thoughts.