Skip to content

Migrating from Tailwind CSS v3 to v4 — Complete Upgrade Guide

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Migrating from Tailwind CSS v3 to v4. We cover key concepts, practical examples, and best practices to help you master this topic.

Migrating from Tailwind CSS v3 to v4 requires moving configuration from tailwind.config.js into CSS @theme blocks, updating the build tooling to Lightning CSS, and adapting variant and plugin usage.

What You'll Learn

You will learn the step-by-step migration Process from v3 to v4, how to convert your JavaScript config to CSS @theme, update build pipelines, and test for breaking changes.

Why It Matters

v4 offers faster builds, smaller bundles, and better DX. DodaTech migrated its entire component library in 2 hours, cutting build time from 3 seconds to 0.3 seconds.

Real-World Use

Doda Browser upgraded from v3 to v4 for its browser extension, reducing the config from 80 lines of JavaScript to 20 lines of CSS @theme directives.

flowchart LR
    A[v3 Config] --> B[Migration]
    B --> C[CSS @theme]
    B --> D[Build Update]
    B --> E[Plugin Update]
    B --> F[Testing]
    style B fill:#38bdf8,stroke:#0284c7,color:#fff
    style C fill:#22c55e,stroke:#16a34a,color:#fff

Step 1: Update Packages

# Remove v3 packages
npm uninstall tailwindcss postcss autoprefixer @tailwindcss/postcss

# Install v4 packages
npm install tailwindcss @tailwindcss/vite

# If using CLI instead of Vite
npm install -g @tailwindcss/cli

Expected output: v4 packages installed. The old PostCSS-based pipeline is replaced.

Step 2: Convert Config to @theme

/* Before: tailwind.config.js (v3) */
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{html,js}'],
  theme: {
    extend: {
      colors: {
        brand: { 500: '#7c3aed', 600: '#6d28d9' },
      },
      fontFamily: {
        display: ['Inter', 'sans-serif'],
      },
    },
  },
  darkMode: 'class',
  plugins: [require('@tailwindcss/forms')],
}

/* After: main.css (v4) */
@import "tailwindcss";

@theme {
  --color-brand-500: #7c3aed;
  --color-brand-600: #6d28d9;
  --font-family-display: "Inter", sans-serif;
}

/* Dark mode strategy */
@variant dark (&:where(.dark, .dark *));

Expected output: All v3 configuration converted to CSS @theme variables. The tailwind.config.js file can be removed.

Step 3: Update Build Setup

// Before: vite.config.js (v3)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  css: {
    postcss: './postcss.config.js',
  },
})

/* After: vite.config.js (v4) */
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
})

Expected output: Vite uses the @tailwindcss/vite plugin directly instead of PostCSS. Remove postcss.config.js.

Step 4: Adapt Custom Variants

/* Before: tailwind.config.js (v3 plugin variant) */
plugin(function({ addVariant }) {
  addVariant('open', 'details[open] &');
})

/* After: main.css (v4 @variant) */
@variant open (&:where(details[open] &));

/* Usage unchanged */
<details class="open:bg-blue-50">
  <summary>Toggle</summary>
  Content
</details>

Expected output: Custom variants defined with @variant in CSS instead of JavaScript plugin. Usage in HTML remains the same.

Step 5: Update Plugins

/* Before: tailwind.config.js (v3) */
module.exports = {
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('@tailwindcss/container-queries'),
  ],
}

/* After: main.css (v4) */
@import "tailwindcss";
@plugin "@tailwindcss/forms";
@plugin "@tailwindcss/typography";

/* Container queries are built-in in v4 - no plugin needed */

Expected output: Plugins use @plugin directive in CSS. Container queries are native in v4.

Step 6: Test and Verify

<!-- Test that common utilities still work -->
<div class="flex items-center justify-between p-4 bg-white dark:bg-gray-900 rounded-lg shadow">
  <h2 class="text-xl font-bold text-gray-900 dark:text-white">Migration Test</h2>
  <button class="bg-brand-500 hover:bg-brand-600 text-white px-4 py-2 rounded transition-colors">
    Button
  </button>
</div>

Expected output: All standard utilities (flex, padding, colors, dark mode) work identically in v4. Custom theme values use the new CSS variable syntax.

Common Mistakes

1. Keeping tailwind.config.js

v4 ignores the JavaScript config file. Remove it after migrating all values to @theme in CSS.

2. Forgetting @plugin Directive

Old require() calls in plugins array do not work. Use @plugin "package-name" in the CSS file.

3. Not Updating Vite Config

Without @tailwindcss/vite plugin, Tailwind v4 does not process. Ensure smooth migration by updating vite.config.js.

4. Using Old Color Syntax

Custom colors in v4 use CSS variables: --color-brand-500: #7c3aed. The old dot-notation in JavaScript config does not apply.

5. Expecting PostCSS Plugins to Work

PostCSS autoprefixer and similar are handled by Lightning CSS. Remove them from the PostCSS config.

Practice Questions

  1. How do you define custom colors in v4? Using CSS variables in @theme: --color-brand-500: #7c3aed.

  2. What replaces the plugins array in v4? The CSS @plugin directive: @plugin "@tailwindcss/forms".

  3. How do you define dark mode Strategy? @variant dark (&:where(.dark, .dark *)); in the CSS file.

  4. What build plugin replaces PostCSS for Vite? @tailwindcss/vite -- import and add to the plugins array.

  5. Are container queries available in v4? Yes, built-in. The @tailwindcss/container-queries plugin is no longer needed.

Challenge

Take an existing v3 project and migrate it to v4. Convert the tailwind.config.js to CSS @theme, update Vite config, replace plugins with @plugin, and verify all components render correctly.

FAQ

Can I keep using PostCSS for other plugins?

Yes. v4 does not prevent PostCSS usage. Keep PostCSS for non-Tailwind plugins and use @tailwindcss/vite for Tailwind processing.

Are all v3 utilities available in v4?

Yes. All v3 utilities are available. Some advanced features have improved syntax.

Will my @apply rules still work?

Yes. @apply continues to work in v4 with the same syntax.

How long does a typical migration take?

A small project takes 30 minutes. A large project with many custom configurations takes 2-4 hours.

Can I run v3 and v4 side by side?

Not recommended. Use separate branches and migrate completely. v3 and v4 cannot coexist in the same build.

Mini Project

Set up a fresh v4 project and migrate a v3 component library (3 components with custom colors, plugins, and dark mode). Document any differences you encounter during migration.

What's Next

With migration complete, explore CSS-First Configuration in depth. Then master Unified Variants for the new variant system.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro