Migrating from v3 to v4

Run the upgrade tool, fix the renamed utilities that fail silently, and verify the result against the old build rather than by eye.

The upgrade tool

# on a clean working tree, on a branch
git switch -c tailwind-v4
git status --short          # must be empty

npx @tailwindcss/upgrade@latest

# then read the diff before running anything
git diff --stat
git diff -- src/app.css tailwind.config.js
  • The tool rewrites the CSS entry point, moves the JavaScript configuration into CSS where it can, and renames the utilities it knows about.
  • It cannot rename a class you built from a variable, so search for anything dynamic before trusting the result.
  • Keep the old build output to compare against: a byte-level diff of the compiled CSS is the most reliable check you have.
  • Commit the migration on its own, with no feature work mixed in, so a visual regression has exactly one candidate cause.
# before the migration
npx @tailwindcss/cli -i src/app.css -o /tmp/before.css --minify

# after
npx @tailwindcss/cli -i src/app.css -o /tmp/after.css --minify
wc -c /tmp/before.css /tmp/after.css

Renamed and removed utilities

v3v4Note
shadow-smshadow-xsThe scale shifted down by one
shadowshadow-smBare shadow now means the old small
rounded-smrounded-xsSame shift
roundedrounded-smBare rounded is the old small
blur-smblur-xsSame shift
outline-noneoutline-hiddenOld behaviour; outline-none now sets outline-style: none
ringring-3Default ring width is now 1px
bg-opacity-50bg-black/50Opacity modifiers replace the opacity utilities
flex-shrink-0shrink-0Renamed
flex-growgrowRenamed
overflow-ellipsistext-ellipsisRenamed
bg-gradient-to-rbg-linear-to-rGradients renamed and extended
decoration-slicebox-decoration-sliceRenamed
⚠️
The default border colour changed from a light grey to currentColor. Every border with no colour class now draws in the text colour, which is the single most visible regression after the upgrade. Add explicit colours, or set a default in a base layer.

Configuration and the container plugin

/* v3: tailwind.config.js with theme.extend
   v4: the same tokens declared in CSS */

@import "tailwindcss";

@theme {
  --color-brand-500: oklch(62% 0.19 250);
  --font-display: "Inter Variable", sans-serif;
  --breakpoint-3xl: 120rem;
  --container-page: 80rem;
  --radius-card: 0.75rem;
  --animate-fade-in: fade-in 250ms ease-out both;

  @keyframes fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
}
/* a legacy JavaScript config still works, explicitly */
@config "../tailwind.config.js";
@import "tailwindcss";
  1. Move tokens into @theme. Every token becomes a CSS variable and a utility at the same time.
  2. Convert plugins: a JavaScript plugin becomes @plugin "package-name", and a custom utility becomes @utility.
  3. The container queries plugin is now in core: use @container on the parent and @sm: on the child.
  4. The container class no longer reads a configuration; use mx-auto max-w-7xl px-4 or define a --container-* token.
  5. Remove corePlugins, safelist and content. Detection is automatic, safelisting is @source inline().
  6. Drop the @tailwind base; @tailwind components; @tailwind utilities; directives - one @import replaces all three.
# find anything the upgrade tool could not rewrite
grep -rn 'bg-opacity-\|flex-shrink-\|flex-grow-\|overflow-ellipsis\|bg-gradient-to-' src --include=*.tsx --include=*.html

# and any class name built dynamically
grep -rnE 'class(Name)?=\{"[^"]*" *\+' src

FAQ

Do I have to migrate the config file to CSS?
No. @config loads an existing JavaScript configuration, which is the pragmatic path when the config is large or generated. Migrating to @theme is worth doing eventually, because the tokens then work as CSS variables in plain CSS as well as in utilities.
How do I check the migration did not change anything visually?
Build both versions to CSS, diff the output, and run a visual comparison of the key screens against the previous release. The compiled CSS diff catches renamed utilities, and the visual comparison catches the ones a rename cannot express - the border colour change being the obvious one.

Configuration and theming Production performance and the honest limits

Last refreshed 2026-09-18.