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.cssRenamed and removed utilities
| v3 | v4 | Note |
|---|---|---|
shadow-sm | shadow-xs | The scale shifted down by one |
shadow | shadow-sm | Bare shadow now means the old small |
rounded-sm | rounded-xs | Same shift |
rounded | rounded-sm | Bare rounded is the old small |
blur-sm | blur-xs | Same shift |
outline-none | outline-hidden | Old behaviour; outline-none now sets outline-style: none |
ring | ring-3 | Default ring width is now 1px |
bg-opacity-50 | bg-black/50 | Opacity modifiers replace the opacity utilities |
flex-shrink-0 | shrink-0 | Renamed |
flex-grow | grow | Renamed |
overflow-ellipsis | text-ellipsis | Renamed |
bg-gradient-to-r | bg-linear-to-r | Gradients renamed and extended |
decoration-slice | box-decoration-slice | Renamed |
⚠️
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";- Move tokens into
@theme. Every token becomes a CSS variable and a utility at the same time. - Convert plugins: a JavaScript plugin becomes
@plugin "package-name", and a custom utility becomes@utility. - The container queries plugin is now in core: use
@containeron the parent and@sm:on the child. - The
containerclass no longer reads a configuration; usemx-auto max-w-7xl px-4or define a--container-*token. - Remove
corePlugins,safelistandcontent. Detection is automatic, safelisting is@source inline(). - Drop the
@tailwind base; @tailwind components; @tailwind utilities;directives - one@importreplaces 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)?=\{"[^"]*" *\+' srcFAQ
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.
Related
Configuration and theming Production performance and the honest limits
Last refreshed 2026-09-18.