Craftly
Back to blog
· 9 min read

Tailwind CSS v4 Complete Migration Guide

Tailwind CSS CSS Migration

Tailwind CSS v4 rewrote almost everything that made v3 great. The config file is gone. The plugin system changed. Colors moved to OKLCH. If you are migrating a real project, expect surprises.

Here is the exact process I use to migrate production apps — including all the traps I fell into so you do not have to.

Why Upgrade at All?

Three reasons:

1. **Speed.** The Oxide engine (Rust-based) is roughly 10x faster at scanning classes. HMR updates feel instant.

2. **Less config.** Design tokens live in your CSS. The `tailwind.config.js` file is gone.

3. **Modern CSS.** Container queries, `@starting-style`, OKLCH colors — all built in.

If you are on a fresh project, skip v3 entirely. If you have a mature v3 codebase, read on.

Step 1: Update Dependencies

Remove the old packages and install v4:

```bash

npm uninstall tailwindcss postcss autoprefixer

npm install tailwindcss@next @tailwindcss/postcss

```

The new `@tailwindcss/postcss` package replaces the old `tailwindcss` PostCSS plugin. It also includes autoprefixing, so you can remove `autoprefixer`.

Step 2: Update PostCSS Config

```js

// postcss.config.mjs

export default {

plugins: {

"@tailwindcss/postcss": {},

},

};

```

That is the entire config. No more `tailwindcss` and `autoprefixer` side by side.

Step 3: Update Your CSS Entry

The three directives from v3 become one import:

```css

/* Before (v3) */

@tailwind base;

@tailwind components;

@tailwind utilities;

/* After (v4) */

@import "tailwindcss";

```

Step 4: Move Config Into CSS

This is the biggest change. Your `tailwind.config.js` dies. Tokens live in CSS.

```css

/* Before (v3) — tailwind.config.js */

module.exports = {

theme: {

extend: {

colors: {

primary: "#6366F1",

secondary: "#EC4899",

},

fontFamily: {

sans: ["Inter", "sans-serif"],

},

},

},

};

/* After (v4) — in your CSS file */

@import "tailwindcss";

@theme inline {

--color-primary: #6366F1;

--color-secondary: #EC4899;

--font-sans: "Inter", sans-serif;

}

```

The `@theme inline` directive generates the utilities automatically. `bg-primary`, `text-secondary`, `font-sans` — all available immediately.

Step 5: Dark Mode

In v3 you set `darkMode: 'class'` in config. In v4 you use the `@variant` directive:

```css

@import "tailwindcss";

@variant dark (&:where(.dark, .dark *));

```

Now `dark:bg-slate-900` works when you toggle the `.dark` class on a parent element. Same API, different plumbing.

Step 6: Plugins

Plugins move from JS to CSS:

```css

/* Before (v3) — tailwind.config.js */

module.exports = {

plugins: [

require("@tailwindcss/typography"),

require("@tailwindcss/forms"),

],

};

/* After (v4) */

@import "tailwindcss";

@plugin "@tailwindcss/typography";

@plugin "@tailwindcss/forms";

```

Custom plugins need rewriting against the v4 API. If you wrote complex JS plugins, budget time for this.

Step 7: Color System Switch to OKLCH

v4 uses OKLCH colors by default. This is a visible change — colors may look slightly different on wide-gamut displays.

```css

@theme inline {

/* OKLCH is preferred in v4 */

--color-brand-500: oklch(0.65 0.2 265);

/* Hex still works */

--color-primary: #6366F1;

}

```

OKLCH gives you:

- Perceptually uniform lightness steps

- P3 color gamut support

- Predictable alpha: `bg-brand-500/50` just works

If you have brand colors in hex, leave them for now. Convert to OKLCH later for better lightness scaling.

Step 8: Browserslist

v4 targets modern browsers only — Chrome 111+, Safari 16.4+, Firefox 128+. If you still need IE or older Safari, stay on v3.

Gotchas I Hit

Gotcha 1: `@apply` Still Works But Feels Dated

You can still `@apply bg-primary text-white` inside component styles. It works. But CSS-native variables feel more v4:

```css

/* v4-native */

.card {

background: var(--color-primary);

color: white;

}

```

Your `@theme inline` tokens become standard CSS variables. Use them directly.

Gotcha 2: Config-in-CSS Means No TypeScript

v3 config could be a `.ts` file. v4 config is CSS. You lose compile-time validation of theme values. Type your design tokens elsewhere if you need that safety net.

Gotcha 3: Some Utilities Renamed

A handful of utilities changed. Check the [migration guide](https://tailwindcss.com/docs/upgrade-guide) for the full list. Common hits:

- `bg-opacity-*` is gone — use `bg-color/50` instead

- `text-opacity-*` is gone — same pattern

Gotcha 4: Build Tools May Need Updates

Vite, Next.js, and most frameworks handle v4 fine. But some custom webpack setups need manual configuration. If you see missing utility classes in production, check your content scanning paths.

Gotcha 5: Container Queries

Container queries are built in. You do not need the plugin anymore:

```html

<div class="@container">

<div class="@md:grid-cols-2">content</div>

</div>

```

Remove `@tailwindcss/container-queries` from dependencies.

A Real Migration Timeline

For a medium-sized Next.js app (150 components, 50 routes):

- **Dependencies + config moves**: 1 hour

- **Plugin rewrites**: 1-2 hours (if you have custom plugins)

- **Visual QA**: 2-3 hours (hunt down color shifts, missing utilities)

- **Dark mode audit**: 1 hour

Total: roughly half a day for a clean pass. Larger apps scale linearly with custom plugin count.

Should You Migrate Today?

- **New project**: Yes. Start on v4.

- **Active project with v3**: Yes, but in a branch. Budget half a day.

- **Legacy project, no active development**: Stay on v3 indefinitely. It still works.

The Payoff

Once you are on v4, every new project starts faster. Fewer config files to maintain. Faster builds. CSS-native tokens that work outside of Tailwind too.

Every template in the [Craftly collection](https://getcraftly.gumroad.com) is built on Tailwind v4 from day one — so if you want a reference implementation to study, start there.

Ready to ship faster?

Browse our collection of production-ready templates.

Browse templates