Tailwind v4 is a rewrite: a new engine, CSS-first configuration, renamed utilities and changed defaults. Most projects migrate in an afternoon because an official tool does the mechanical work, but a few behaviour changes must be checked by hand. In this lesson you will run the upgrade tool, understand the changes it makes, and work through the checklist of things it cannot detect.
Commit or stash your work, create a branch, and run the tool from the project root:
git switch -c tailwind-v4
npx @tailwindcss/upgradeIt requires Node.js 20 or later. The tool updates dependencies, rewrites tailwind.config.js into @theme CSS, replaces the @tailwind directives with @import "tailwindcss", switches PostCSS to @tailwindcss/postcss, and renames utilities in your templates. Review the diff before continuing; the rewrite is conservative and leaves comments where it needs a decision.
The manual equivalent of what the tool does to your stylesheet:
/* v3 */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* v4 */
@import "tailwindcss";
@theme {
--color-brand: #2563eb; /* was theme.extend.colors.brand */
--font-sans: "Inter", sans-serif;
--breakpoint-3xl: 120rem; /* was theme.extend.screens */
}Anything the tool cannot express in CSS stays in the config file, loaded with @config "./tailwind.config.js"; — treat that as temporary. Plugins become @plugin "@tailwindcss/typography";, class-based dark mode becomes @custom-variant dark (&:where(.dark, .dark *));, and a prefix becomes prefix(tw) on the import with classes written tw:flex. Sass, Less and Stylus are not supported; v4 handles nesting and imports itself.
The scale for shadows, radii, blurs and rings shifted one step:
| v3 | v4 |
|---|---|
| shadow-sm / shadow | shadow-xs / shadow-sm |
| rounded-sm / rounded | rounded-xs / rounded-sm |
| blur-sm / blur | blur-xs / blur-sm |
| ring (3px) | ring-3 |
| outline-none | outline-hidden |
| bg-gradient-to-r | bg-linear-to-r |
| !flex | flex! |
| bg-[--brand] | bg-(--brand) |
Removed entirely: the *-opacity-* utilities (use /50 modifiers), flex-shrink-*/flex-grow-* (use shrink-*/grow-*), overflow-ellipsis (text-ellipsis) and decoration-slice (box-decoration-slice). The upgrade tool rewrites all of these.
These do not show up as errors; they change how existing markup looks:
currentColor, not gray-200. Bare border and divide-y need an explicit color, or a global default in @layer base.currentColor instead of 3px blue; focus styles that relied on ring need ring-3 ring-blue-500.first:*:pt-0 becomes *:first:pt-0.hover: applies only on devices that support hover; restore the old behaviour with @custom-variant hover (&:hover); if required.cursor: default, and dialog margins are reset.container no longer reads the center and padding options. Recreate them as a utility:@utility container {
margin-inline: auto;
padding-inline: 2rem;
}Build with both versions and compare output size and screenshots of key pages. Search for what the tool cannot see: class names assembled in JavaScript, classes in CMS content, and any old safelist, which becomes @source inline(). Finally delete tailwind.config.js once @config is unnecessary, remove autoprefixer and postcss-import (both built in), and drop plugins that are now core features.
bg-opacity-* classes in JavaScript-built strings where the tool could not rewrite them.What does the v3 utility `shadow` become in v4?
npx @tailwindcss/upgrade on a clean branch handles dependencies, config-to-CSS conversion and utility renames.@tailwind directives become @import "tailwindcss" and tailwind.config.js becomes @theme; @config bridges the rest.!, bg-[--var] and gradient names changed form.container and Preflight tweaks.safelist entries with @source inline() and remove plugins that are now built in.Next lesson: Building a Complete Landing Page with Tailwind — combine everything from the course into a polished, responsive landing page.