Overview

Migrate to Tailwind one component at a time: install v4, remove CSS that duplicates its base styles, move your design tokens into @theme, then convert components and delete their old rules as you go. Tailwind v4 is configured in CSS, with no tailwind.config.js required. Reference: tailwind; comparison with CSS Modules: tailwind-vs-css-modules.

Prerequisites

  • Node 20 or newer.
  • Plain CSS or PostCSS source. Tailwind v4 does not work with Sass, Less, or Stylus; convert those files to plain CSS first.
  • Target browsers of Safari 16.4, Chrome 111, and Firefox 128 or newer. Older browsers need Tailwind 3.4.

Steps

1. Install

npm install tailwindcss @tailwindcss/vite      # Vite-based projects
npm install tailwindcss @tailwindcss/postcss postcss   # Next.js and other PostCSS setups
// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
export default { plugins: [tailwindcss()] };
// postcss.config.mjs
export default { plugins: { "@tailwindcss/postcss": {} } };

Add @import "tailwindcss"; at the top of your main CSS file. For other frameworks, follow Tailwind’s framework guide for the same package choice. The standalone CLI is npx @tailwindcss/cli -i in.css -o out.css.

2. Remove duplicate resets

Tailwind’s Preflight already sets box-sizing: border-box, zeroes body margin, and makes images max-width: 100%. Delete those rules from your stylesheet and keep resets Preflight lacks, such as scrollbar styling. Note that v4’s default border color is currentColor, so add an explicit color wherever a bare border class relied on the old gray.

3. Move tokens into @theme

@import "tailwindcss";
 
@theme {
  --color-brand: #1a56db;
  --color-brand-dark: #1e429f;
  --font-sans: "Inter", ui-sans-serif, system-ui;
}

This generates bg-brand, text-brand-dark, and font-sans. Names must follow the namespace (--color-*, --font-*, --spacing); a name like --brand-blue generates nothing. Spacing needs no per-value tokens: --spacing is a base unit (0.25rem), so m-18 already works. Use @theme inline when a token refers to another variable. See css-custom-properties.

4. Convert one component per commit

Start with a small isolated component.

<!-- before: <button class="btn-primary">Save</button> -->
<button class="bg-brand text-white px-4 py-2 rounded-md font-semibold">Save</button>

Delete the old .btn-primary rule, build, and commit. For patterns used in many places, keep the class name temporarily:

@utility btn-primary {
  @apply bg-brand text-white px-4 py-2 rounded-md font-semibold;
}

In v4, @utility defines custom utilities that work with variants; @layer components still works for plain component classes. The ! important modifier now goes last: bg-white!.

5. Sort classes with Prettier

npm install -D prettier prettier-plugin-tailwindcss
{ "plugins": ["prettier-plugin-tailwindcss"], "tailwindStylesheet": "./src/main.css" }

With v4, tailwindStylesheet must point at the CSS file that holds @import "tailwindcss" and your @theme. The plugin must load last if you use other Prettier plugins. Run prettier --check in CI.

Verify it worked

npm run build
grep -rn "\.btn-" src --include="*.css"     # no output: converted rules are gone

Open the page and confirm the converted component matches its old appearance, including hover and focus states.

Common errors

  • Utilities missing in the build. The class name is assembled dynamically ("bg-" + color), so the scanner never sees it; write full class names, or add @source for paths it does not scan.
  • A theme token produces no utility. The name is outside a Tailwind namespace.
  • A third-party stylesheet beats utilities. Import it into a lower layer (@import "lib.css" layer(base);), or use the ! modifier sparingly.
  • Prettier does not sort. tailwindStylesheet is missing or the plugin is not last.

FAQ

Can I use Tailwind v4 with Sass or Less?

No. Tailwind v4 does not work with Sass, Less, or Stylus, so convert those files to plain CSS first.

Do I still need tailwind.config.js in Tailwind v4?

No. Tailwind v4 is configured in CSS, and design tokens move into @theme.

Why does my @theme token not generate a utility class?

The name is outside a Tailwind namespace. Use --color-*, --font-*, or --spacing; a name like --brand-blue generates nothing.

Which browsers does Tailwind v4 support?

Safari 16.4, Chrome 111, and Firefox 128 or newer; older browsers need Tailwind 3.4.

Why are some Tailwind classes missing from the build?

The class name is assembled dynamically, so the scanner never sees it. Write full class names, or add @source for paths it does not scan.