🚀 Migrating from Tailwind CSS v3 to v4

Everything you need to know for a smooth transition

So you've been working with Tailwind CSS v3 for a while now, and you've heard about v4. Maybe you're wondering if it's worth the upgrade, or perhaps you're already sold and just need to know how to make the jump. Either way, you're in the right place. I've been through this migration myself on several projects, and honestly? It's way less painful than you might think.

Why Should You Even Bother Upgrading?

Before we dive into the "how," let's talk about the "why." Tailwind v4 isn't just a minor version bump—it brings some genuinely exciting improvements that make it worth your time.

Key Benefits of v4:
  • Lightning-fast builds – We're talking up to 10x faster compilation times thanks to the new Rust-based engine
  • Native CSS variables – No more PostCSS gymnastics for custom properties
  • Better developer experience – Improved error messages that actually help you fix problems
  • Smaller bundle sizes – More efficient CSS output means faster page loads
  • Modern CSS features – Built-in support for container queries, cascade layers, and more

Before You Start: Prerequisites

Let's make sure you've got everything you need before jumping in. Trust me, spending five minutes here will save you hours of head-scratching later.

What you'll need:
  • Node.js 16.0 or higher (v4 won't work with older versions)
  • A basic understanding of your build configuration
  • A backup of your project (just in case—better safe than sorry)
  • About 30-60 minutes of your time

Step 1: Update Your Dependencies

First things first—let's update Tailwind itself. This is pretty straightforward, but there are a few things to watch out for.

For npm users:

npm uninstall tailwindcss postcss autoprefixer
npm install tailwindcss@latest

For yarn users:

yarn remove tailwindcss postcss autoprefixer
yarn add tailwindcss@latest

For pnpm users:

pnpm remove tailwindcss postcss autoprefixer
pnpm add tailwindcss@latest
Wait, why are we removing PostCSS and Autoprefixer?
Good question! In v4, Tailwind comes with its own built-in CSS processor. You don't need PostCSS or Autoprefixer anymore unless you're using them for other purposes. This simplifies your setup and speeds up builds.

Step 2: Update Your Configuration File

Here's where things get interesting. Tailwind v4 introduces a new configuration approach that's more aligned with modern CSS. Instead of using tailwind.config.js, you'll now define customizations directly in CSS using the new @theme directive.

Your old v3 configuration might look like this:

// tailwind.config.js (v3)
module.exports = {
  content: ['./src/**/*.{html,js,jsx,tsx}'],
  theme: {
    extend: {
      colors: {
        'brand-blue': '#0066cc',
        'brand-purple': '#764ba2',
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
      },
      spacing: {
        '128': '32rem',
      },
    },
  },
  plugins: [],
}

In v4, you'll convert this to CSS:

/* styles.css (v4) */
@import "tailwindcss";

@theme {
  --color-brand-blue: #0066cc;
  --color-brand-purple: #764ba2;
  
  --font-family-sans: Inter, system-ui, sans-serif;
  
  --spacing-128: 32rem;
}

I know what you're thinking—"Wait, I have to rewrite my entire config?" Don't worry, it's actually pretty intuitive once you get the hang of it. And here's the cool part: these are just CSS custom properties, which means they work exactly like you'd expect CSS variables to work.

Important naming convention: Notice how the property names follow a pattern? --color-* for colors, --font-family-* for fonts, --spacing-* for spacing. This isn't arbitrary—it's how Tailwind maps your custom properties to utility classes.

Step 3: Update Your CSS Imports

Your CSS entry file needs a little update too. The good news? It's simpler than before.

Old way (v3):

/* styles.css (v3) */
@tailwind base;
@tailwind components;
@tailwind utilities;

New way (v4):

/* styles.css (v4) */
@import "tailwindcss";

Yep, that's it. One line instead of three. The new import automatically includes everything you need—base styles, components, and utilities.

Step 4: Update Your Build Configuration

Since v4 has its own built-in processor, you can simplify your build setup significantly.

If you're using Vite:

// vite.config.js
import tailwindcss from 'tailwindcss'

export default {
  css: {
    postcss: {
      plugins: [tailwindcss()],
    },
  },
}

If you're using Next.js:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Tailwind v4 works out of the box with Next.js 14+
}

module.exports = nextConfig

If you're using Webpack:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader', 'tailwindcss'],
      },
    ],
  },
}

Step 5: Migrate Custom Components

If you've been using @layer components for custom component classes, you'll need to adjust your approach slightly.

Old approach (v3):

/* v3 approach */
@layer components {
  .btn-primary {
    @apply bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600;
  }
}

New approach (v4):

/* v4 approach - Method 1: CSS */
.btn-primary {
  background-color: theme(colors.blue.500);
  color: theme(colors.white);
  padding: theme(spacing.4) theme(spacing.2);
  border-radius: theme(borderRadius.DEFAULT);
}

.btn-primary:hover {
  background-color: theme(colors.blue.600);
}

/* v4 approach - Method 2: Still using @apply */
.btn-primary {
  @apply bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600;
}

The @apply directive still works in v4, but the team recommends moving toward standard CSS when possible. The theme() function lets you access your design tokens directly in CSS.

Step 6: Handle Breaking Changes

Okay, here's where we need to talk about the stuff that might actually break. Don't panic—there aren't many breaking changes, and most codebases won't hit all of them.

Content Configuration

The content option now lives in your CSS file, not in a JavaScript config.

/* styles.css */
@import "tailwindcss";

@source "../../**/*.html";
@source "../../**/*.jsx";
@source "../../**/*.tsx";

Color Opacity Modifiers

The syntax for color opacity has changed slightly. Instead of using arbitrary values with slash notation, you now use direct opacity modifiers.

v3 Syntax v4 Syntax
bg-blue-500/50 bg-blue-500/50 (same!)
text-[#0066cc]/75 text-[#0066cc]/75 (same!)

Actually, good news here—the color opacity syntax mostly stays the same!

Important Prefix Changes

If you were using the important option, the syntax has changed:

/* v3 config.js */
module.exports = {
  important: true,
}

/* v4 styles.css */
@import "tailwindcss";
@utility important;

Step 7: Update Plugins (If You Use Any)

This is probably the trickiest part of the migration. Some popular Tailwind plugins haven't been updated for v4 yet. Here's what I've found works:

Plugin compatibility checklist:
  • âś… @tailwindcss/typography – Fully compatible, just update to latest version
  • âś… @tailwindcss/forms – Works great in v4
  • âś… @tailwindcss/container-queries – Now built into v4 core!
  • ⚠️ Custom plugins – May need rewriting; check the plugin API docs

Installing compatible plugins:

npm install @tailwindcss/typography@latest @tailwindcss/forms@latest

Using plugins in v4:

/* styles.css */
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";

Step 8: Test Everything

Alright, you've made it through the migration steps. Now comes the important part—actually making sure everything works. I've learned this the hard way: don't skip testing.

Testing checklist:
  • Run your build process and make sure it completes without errors
  • Check your browser console for any CSS-related warnings
  • Visually inspect all major pages of your application
  • Test responsive breakpoints (resize your browser window)
  • Check dark mode if you're using it
  • Verify any custom components still look correct
  • Run your automated tests if you have them

Common Issues and How to Fix Them

Even with careful migration, you might run into some hiccups. Here are the most common ones I've encountered and how to solve them:

Issue 1: Build is failing with "Cannot find module 'tailwindcss'"

This usually means your build tool isn't configured correctly for v4.

// Make sure you're importing it correctly
import tailwindcss from 'tailwindcss'

// Not 'tailwindcss/plugin' or other old imports

Issue 2: Custom colors aren't working

Double-check your CSS variable naming. Remember, it needs to follow the --color-* pattern:

/* Wrong */
@theme {
  --brand-blue: #0066cc;
}

/* Right */
@theme {
  --color-brand-blue: #0066cc;
}

Issue 3: Styles appear different after migration

This is usually due to the improved default color palette or spacing scale. You might need to explicitly set values that were previously using defaults:

@theme {
  /* Lock in specific values if needed */
  --color-blue-500: #3b82f6;
  --spacing-4: 1rem;
}

Issue 4: Build times are slower than expected

If you're not seeing the speed improvements, make sure you've removed the old PostCSS config:

// Delete or simplify postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},  // Remove these if only used for Tailwind
  },
}

Advanced Migration Topics

Working with Design Tokens

One of the coolest things about v4 is how it handles design tokens. You can now create semantic color systems more easily:

@theme {
  /* Base colors */
  --color-primary: #0066cc;
  --color-secondary: #764ba2;
  --color-accent: #f59e0b;
  
  /* Semantic colors */
  --color-success: #10b981;
  --color-error: #ef4444;
  --color-warning: #f59e0b;
  
  /* Surface colors for dark mode support */
  --color-surface: #ffffff;
  --color-surface-dark: #1e293b;
}

/* Use them with dark mode */
.card {
  background: theme(colors.surface);
}

.dark .card {
  background: theme(colors.surface-dark);
}

Setting Up Container Queries

Remember how you needed a plugin for this in v3? It's built right in now:


The @ prefix indicates container query breakpoints instead of viewport breakpoints. This is incredibly useful for component-based designs.

Performance Optimization Tips

Now that you're on v4, here are some ways to squeeze even more performance out of it:

1. Use CSS Cascade Layers

@import "tailwindcss";

@layer base {
  /* Your base styles */
}

@layer components {
  /* Component styles */
}

@layer utilities {
  /* Custom utilities */
}

2. Optimize Content Sources

Be specific about where Tailwind should look for classes:

/* Instead of this: */
@source "../../**/*"

/* Do this: */
@source "../../components/**/*.tsx"
@source "../../pages/**/*.tsx"
@source "../../app/**/*.tsx"

3. Enable JIT Mode Features

v4 has JIT mode enabled by default, but you can take advantage of arbitrary values more liberally now:


Fast arbitrary values!

Migrating a Real-World Project: A Case Study

Let me walk you through how I migrated a mid-sized e-commerce project from v3 to v4. This might help you understand what to expect with your own projects.

The project: An online store with about 50 pages, custom components, and heavy use of Tailwind customization.

Time taken: About 2 hours including testing.

Issues encountered:

  1. Custom plugin incompatibility – We had a custom plugin for generating utility classes. Had to rewrite it using the new plugin API. Took about 30 minutes.
  2. Build configuration – Needed to update our Webpack config. The documentation made this straightforward.
  3. Color inconsistencies – A few colors looked slightly different. We locked in specific hex values in our theme configuration to match the old design exactly.

Benefits we saw immediately:

Should You Migrate Right Now?

This is the question everyone asks, and honestly? It depends on your situation.

Migrate now if:
  • You're starting a new project (no-brainer)
  • Your current build times are frustrating you
  • You want to use modern CSS features like container queries
  • Your project isn't super time-sensitive and you can afford a few hours for migration
Wait a bit if:
  • You're relying heavily on custom plugins that aren't v4-compatible yet
  • You're in the middle of a major deadline
  • Your project is working fine and you don't have time for potential troubleshooting

Helpful Resources

Here are the resources I found most helpful during my migrations:

Final Thoughts

Look, I'm not going to lie and say this migration is completely effortless. It requires some work, especially if you have a large codebase with lots of customization. But after migrating several projects, I can confidently say it's worth it.

The performance improvements alone make a huge difference in the daily development experience. And the new CSS-first configuration approach? It felt weird at first, but now it makes so much more sense. No more context-switching between JavaScript and CSS—it's all just CSS.

My advice? Set aside a few hours, follow this guide, and take it step by step. Don't try to rush it. And definitely test thoroughly before deploying to production.

The Tailwind team has done an excellent job making this transition as smooth as possible. They've thought through most of the common pain points and provided good solutions. The breaking changes are minimal, and the benefits are substantial.

If you run into issues during your migration, don't hesitate to reach out to the community. We've all been there, and most problems have already been solved by someone else.

Quick Summary:
  1. Update dependencies (remove PostCSS/Autoprefixer)
  2. Convert config.js to CSS with @theme
  3. Update CSS imports to single @import
  4. Update build configuration
  5. Migrate custom components
  6. Handle breaking changes
  7. Update plugins
  8. Test everything thoroughly

Good luck with your migration! You've got this. And honestly? Once you're done, you're going to wonder why you didn't do it sooner.

Found this guide helpful? The Tailwind CSS community is always growing and learning together. Don't forget to document your own migration experience—it might help someone else down the line!