Deprecated APIs
Every utility, directive, config option and package removed, renamed or deprecated in Tailwind v4 — what replaced it, and how to find it in your codebase.
A lookup table for anything that stopped working, changed meaning, or is on its way out. Use it when a v3 snippet from a blog post does not do what the author promised — most of the time the class still compiles, it just means something else now.
The dangerous entries are marked. A removed class produces no CSS, which you notice immediately. A renamed class where the old name still exists produces the wrong CSS, which you may not notice for weeks.
Opacity utilities#
Removed The *-opacity-* family is gone. Every colour utility takes a / opacity modifier instead.
| Removed | Replacement |
|---|---|
bg-opacity-* | bg-black/50 |
text-opacity-* | text-black/50 |
border-opacity-* | border-black/50 |
divide-opacity-* | divide-black/50 |
ring-opacity-* | ring-black/50 |
placeholder-opacity-* | placeholder-black/50 |
<!-- v3 — two classes, and the opacity is a separate axis -->
<div class="bg-indigo-500 bg-opacity-50 hover:bg-opacity-75">…</div>
<!-- v4 — one class, composes with variants and arbitrary colours -->
<div class="bg-indigo-500/50 hover:bg-indigo-500/75">…</div>The old family existed because v3 implemented colours as an RGB triple plus a separate --tw-bg-opacity variable. v4 uses color-mix(), so the modifier works on any colour, including bg-(--brand)/40.
Flex utilities#
Removed The flex- prefix was dropped from grow and shrink.
| Removed | Replacement |
|---|---|
flex-grow | grow |
flex-grow-0 | grow-0 |
flex-shrink | shrink |
flex-shrink-0 | shrink-0 |
<!-- v3 -->
<div class="flex-grow flex-shrink-0">…</div>
<!-- v4 -->
<div class="grow shrink-0">…</div>flex-1, flex-auto, flex-initial and flex-none are unchanged — they set the flex shorthand, not the individual longhands.
Text overflow#
Removed
| Removed | Replacement |
|---|---|
overflow-ellipsis | text-ellipsis |
text-ellipsis and text-clip had already existed alongside overflow-ellipsis in v3; v4 simply dropped the duplicate. Nothing about the behaviour changed — text-ellipsis still needs overflow-hidden and whitespace-nowrap to do anything.
Box decoration#
Removed
| Removed | Replacement |
|---|---|
decoration-slice | box-decoration-slice |
decoration-clone | box-decoration-clone |
The short names conflicted with the decoration-* text-decoration utilities (decoration-2, decoration-indigo-500, decoration-wavy), which are not deprecated. Only the two box-decoration aliases were removed.
The size-scale renames#
Renamed — old names still compile
This is the most important section on the page. Five scales were shifted so that every step has an explicit name, with no unnamed bare utility sitting between xs and md.
| v3 | v4 |
|---|---|
shadow-sm | shadow-xs |
shadow | shadow-sm |
drop-shadow-sm | drop-shadow-xs |
drop-shadow | drop-shadow-sm |
blur-sm | blur-xs |
blur | blur-sm |
backdrop-blur-sm | backdrop-blur-xs |
backdrop-blur | backdrop-blur-sm |
rounded-sm | rounded-xs |
rounded | rounded-sm |
<!-- Written for v3: a medium shadow and a 2px radius -->
<div class="rounded shadow">…</div>
<!-- The same visual result in v4 -->
<div class="rounded-sm shadow-sm">…</div>Reading the mapping in reverse is the useful direction: if you are looking at v3 code, add a step. If you are looking at v4 code, it means what it says.
The larger steps — shadow-md, shadow-lg, shadow-xl, shadow-2xl, rounded-md and up — are unchanged.
Outline#
outline-none#
Meaning changed The class still exists in v4 and does something different.
| Class | v3 | v4 |
|---|---|---|
outline-none | outline: 2px solid transparent — invisible in normal rendering, but still drawn in forced-colours mode | outline-style: none — the outline is genuinely removed |
outline-hidden | did not exist | the behaviour v3 called outline-none |
<!-- v3: hide the default outline, draw your own ring -->
<button class="focus:outline-none focus:ring-2 focus:ring-indigo-500">…</button>
<!-- v4: same intent, and still visible in Windows High Contrast mode -->
<button class="focus:outline-hidden focus:ring-2 focus:ring-indigo-500">…</button>Use outline-none deliberately, and only when nothing else supplies the focus indicator.
outline on its own#
Behaviour changed In v3, outline set outline-style: solid and you paired it with a width. In v4 the width utilities imply the style, so the pairing is unnecessary.
<!-- v3 -->
<div class="outline outline-2 outline-indigo-500">…</div>
<!-- v4 -->
<div class="outline-2 outline-indigo-500">…</div>Ring#
Default changed
| v3 | v4 | |
|---|---|---|
Bare ring width | 3px | 1px |
| Default ring colour | blue-500 | currentColor |
ring-3 reproduces the v3 width, and the colour must now be stated:
<!-- v3 -->
<button class="ring">…</button>
<!-- Identical output in v4 -->
<button class="ring-3 ring-blue-500">…</button>The v3 default was a debugging convenience that shipped to production in a lot of codebases. v4's currentColor default follows the same rule as border, but it means a bare ring on dark text is a thin dark line — easy to miss as a focus indicator. State the width and the colour.
Gradients#
Renamed — alias retained
| v3 | v4 |
|---|---|
bg-gradient-to-r | bg-linear-to-r |
bg-gradient-to-br | bg-linear-to-br |
bg-gradient-to-* | bg-linear-to-* |
The rename made room for bg-radial-* and bg-conic-*, which are new in v4. The old names still work as deprecated aliases, so this is the one rename with no visual consequence — but new code should use bg-linear-*.
There is also a behaviour change with no rename attached: gradient stops now persist across variants. In v3 a dark: override reset the stops you did not mention; in v4 they are preserved, so a three-stop gradient stays three-stop unless you reset it:
<div class="bg-linear-to-r from-red-500 via-orange-400 to-yellow-400
dark:from-blue-500 dark:via-none dark:to-teal-400">…</div>via-none is the explicit reset back to a two-stop gradient.
Configuration file options#
Removed There is no config file in v4. @config still loads one, but several keys are ignored inside it — silently.
| v3 option | v4 |
|---|---|
content | Automatic source detection, plus @source for anything outside it |
theme | @theme in CSS |
theme.extend | @theme — declaring a value adds it; the namespace is only replaced if you reset it with initial |
plugins | @plugin "…" |
prefix: 'tw-' | @import "tailwindcss" prefix(tw); — and the prefix moves to the front as a variant: tw:flex |
darkMode | @custom-variant dark (…) |
container.center / container.padding | Redefine the utility with @utility container { … } |
corePlugins | Unsupported. Remove a whole namespace in @theme instead — --color-*: initial |
safelist | Unsupported. Use @source inline(…) |
separator | Unsupported. The separator is : |
The replacement for safelist is @source inline(), which brace-expands:
@import "tailwindcss";
@source inline("underline");
@source inline("{hover:,focus:,}underline");
@source inline("bg-red-{50,{100..900..100},950}");@source not inline(…) does the opposite, preventing a class from being generated even when it appears in your source.
prefix deserves its own note because the syntax moved as well as the config:
<!-- v3 -->
<div class="tw-flex tw-bg-red-500 hover:tw-bg-red-600">…</div>
<!-- v4 — the prefix is a variant-like token at the start -->
<div class="tw:flex tw:bg-red-500 tw:hover:bg-red-600">…</div>The theme() function#
Deprecated, still works
Every theme value is a real CSS variable in v4, so reach for the variable:
/* Deprecated */
.card {
background-color: theme(colors.red.500);
padding: theme(spacing.4);
}
/* v4 */
.card {
background-color: var(--color-red-500);
padding: --spacing(4);
}theme() is still the answer inside media queries, where custom properties are not permitted — but the argument changed from dot notation to the variable's name:
/* v3 */
@media (width >= theme(screens.xl)) { … }
/* v4 */
@media (width >= theme(--breakpoint-xl)) { … }Related removals in the JavaScript API:
| Removed | Replacement |
|---|---|
resolveConfig() | Read the CSS variables — getComputedStyle(document.documentElement).getPropertyValue('--shadow-xl') |
Importing tailwindcss/defaultTheme for values | The generated CSS variables, or @theme static to force them all to be emitted |
The v3 Play CDN#
Wrong version
<!-- v3 Play CDN — does not understand any v4 syntax -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- v4 -->
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>https://cdn.tailwindcss.com still serves Tailwind v3, so a page using it will render @tailwind directives and a tailwind.config object happily — and reject @theme, @utility and every v4 class rename. It is the fastest way to end up debugging a "v4 bug" that is actually v3.
Configuration also moved. v3's inline config object becomes a type="text/tailwindcss" style block:
<!-- v3 -->
<script>tailwind.config = { theme: { extend: { colors: { brand: '#4f46e5' } } } }</script>
<!-- v4 -->
<style type="text/tailwindcss">
@theme { --color-brand: oklch(0.51 0.23 277); }
</style>Neither CDN belongs in production — see Installation.
npx tailwindcss init#
Removed There is no config file to generate, so the command was removed along with -p (which also wrote a postcss.config.js).
npx tailwindcss init -p # v3 only — the command no longer existsThere is no v4 equivalent to run. Create the CSS file yourself:
@import "tailwindcss";A tutorial that opens with npx tailwindcss init was written for v3. Treat everything after it with suspicion.
The CLI binary moved as well: npx tailwindcss -i in.css -o out.css is the v3 CLI. In v4 it is npx @tailwindcss/cli -i in.css -o out.css.
darkMode: 'class'#
Removed There is no darkMode option, because there is no config. @custom-variant replaces it and covers cases the option never could.
module.exports = { darkMode: 'class' }@custom-variant dark (&:where(.dark, .dark *));For a data-theme attribute instead of a class:
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));darkMode: 'media' was v3's default and needs no replacement — following the OS preference is v4's default too. Full detail in Dark Mode.
The @tailwind directives#
Removed
@tailwind base;
@tailwind components;
@tailwind utilities;@import "tailwindcss";Unlike most entries on this page, this one fails loudly — the directives are not recognised and the build errors. That is the good case.
If your v3 setup deliberately omitted a layer, the pieces are available as separate imports:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities);Omitting @import "tailwindcss/preflight.css" layer(base); is how you opt out of Preflight.
Plugins replaced by core#
Uninstall these
| Plugin | Now in core as |
|---|---|
@tailwindcss/line-clamp | line-clamp-1 … line-clamp-none |
@tailwindcss/aspect-ratio | aspect-square, aspect-video, aspect-[4/3], and the --aspect-* theme namespace |
@tailwindcss/container-queries | @container, @sm: … @7xl:, @max-md:, and --container-* |
Leaving them installed is not harmless: you end up with two definitions of the same class names competing in the cascade, and the plugin version usually wins by source order.
@tailwindcss/typography and @tailwindcss/forms are not deprecated. Load them with @plugin:
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms" {
strategy: class;
}How to find them in your codebase#
Run these from the project root. rg is ripgrep; the grep equivalents are given where the pattern is portable.
Removed classes — these produce no CSS, so every hit is a real bug:
rg -n '\b(bg|text|border|divide|ring|placeholder)-opacity-[0-9]+'
rg -n '\bflex-(grow|shrink)\b'
rg -n '\boverflow-ellipsis\b'
rg -n '\bdecoration-(slice|clone)\b'Renamed size utilities — these still compile, so every hit needs a human decision:
rg --pcre2 -n '(?<=["\s])(shadow|drop-shadow|blur|backdrop-blur|rounded)(?=["\s])'
rg -n '\b(shadow|drop-shadow|blur|backdrop-blur|rounded)-sm\b'The first pattern finds bare utilities whose meaning shifted one step down. The second finds the -sm steps, which in v4 mean what -xs means in the v3 scale.
The lookarounds need ripgrep's PCRE2 engine (--pcre2 or -P). Without it, grep -REn '(^|[" ])(shadow|rounded|blur)([" ]|$)' src/ gets close, with more false positives.
Outline, ring and gradients:
rg -n '\boutline-none\b'
rg --pcre2 -n '(?<=["\s])ring(?=["\s])'
rg -n '\bbg-gradient-to-'Syntax that moved:
rg --pcre2 -n '(?<=["\s])!(?=[a-z])' # !flex -> flex!
rg -n '\[--[a-z]' # bg-[--brand] -> bg-(--brand)
rg -n '\[[a-z0-9%.]+,[a-z0-9%.]+\]' # commas in arbitrary values -> underscoresBuild setup and config:
rg -n 'cdn\.tailwindcss\.com'
rg -n '@tailwind\s+(base|components|utilities)'
rg -n 'tailwindcss init'
rg -n 'resolveConfig|tailwindcss/defaultTheme'
rg -n 'corePlugins|safelist|separator|darkMode' -g 'tailwind.config.*'
rg -n '@tailwindcss/(line-clamp|aspect-ratio|container-queries)' -g 'package.json'
rg -n 'theme\(' -g '*.css'Dynamic class names, which used to need safelist:
rg -n '(bg|text|border)-\$\{' # template literals
rg -n '`(bg|text|border)-' # any interpolated class stringCommon mistakes#
- Assuming a green build means a clean migration. Most entries on this page still compile.
shadow,rounded,blur,ringandoutline-noneare all valid v4 classes with different meanings. - Leaving
focus:outline-nonealone because it still compiles. The class name did not change; the behaviour did. If you are hiding an outline to draw your own ring, you wantoutline-hidden. - Keeping the container-queries or line-clamp plugin "just in case". Two definitions of the same utilities is worse than either one alone.
- Trusting
@configto carry asafelist. It is ignored in silence — the classes simply are not generated. - Reading
theme()as removed. It is deprecated, not gone, and it is still correct inside media queries with the newtheme(--breakpoint-xl)argument form. - Using
https://cdn.tailwindcss.comto test a v4 snippet. That URL serves v3, so you are debugging the wrong version.
Related topics#
- v3 → v4 Migration — the upgrade tool and the full step-by-step
- Legacy Projects — when to stay on v3.4, and reading v3 tutorials safely
- Theme & Design Tokens — the
@themereplacement fortailwind.config.js - Functions & Directives —
@source,@plugin,@config,--spacing() - Borders — outlines, rings and the new default border colour