Wood Chen

Complete Guide to Migrating a Next.js + shadcn/ui Project from Tailwind CSS v3 to v4

0 comments231 views408 words

This post was translated from Chinese by AI. If anything reads oddly, the Chinese original is authoritative. 中文原文

Introduction

Tailwind CSS v4 was officially released on January 22, 2025, bringing revolutionary changes. Based on my experience migrating a Next.js 15 + shadcn/ui project, this article provides a complete migration guide.

Overview of Key Changes

🚀 Core Improvements

  • Performance boost: 5 times faster builds and 100+ times faster incremental builds
  • CSS-first configuration: Configure in CSS instead of JavaScript
  • Modern CSS features: Uses new features such as cascade layers and @property
  • Better developer experience: Microsecond-level incremental builds

⚠️ Compatibility Requirements

  • Browser support: Safari 16.4+, Chrome 111+, Firefox 128+
  • Next.js: Fully compatible, but requires manual configuration
  • shadcn/ui: Officially supports v4

Migration Steps

1. Upgrade Tailwind CSS

# Upgrade to v4
npm install tailwindcss@latest

# Install the required PostCSS plugin
npm install @tailwindcss/postcss

2. Update the PostCSS Configuration

Update postcss.config.mjs to:

/** @type {import('postcss-load-config').Config} */
const config = {
  plugins: ["@tailwindcss/postcss"],
};

export default config;

3. Delete the Old Configuration File

# Delete the Tailwind v3 configuration file
rm tailwind.config.ts
# Or
rm tailwind.config.js

4. Migrate to CSS-first Configuration

4.1 Update globals.css

Replace:

@tailwind base;
@tailwind components;
@tailwind utilities;

with:

@import "tailwindcss";
@plugin "tailwindcss-animate";

4.2 Migrate the Theme Configuration

Move the configuration from tailwind.config.ts into CSS:

@theme {
  /* Font configuration */
  --font-sans: -apple-system, BlinkMacSystemFont, 'Noto Sans SC', system-ui, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-mono: 'SF Mono', Monaco, 'Inconsolata', 'Roboto Mono', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
  
  /* Container configuration - replaces the original container configuration */
  --container-center: true;
  --container-padding: 2rem;
  --container-max-width-2xl: 1400px;
}

@theme inline {
  /* shadcn/ui color variable mappings */
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  
  /* Border radius configuration */
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
}

4.3 Keep the shadcn/ui Color Variables

Keep the existing CSS variable definitions:

:root {
  --background: oklch(0.9766 0.0017 67.8025);
  --foreground: oklch(0 0 0);
  --card: oklch(1.0000 0 0);
  /* ... Other color variables */
  --radius: 0.5rem;
}

.dark {
  --background: oklch(0.2178 0 0);
  --foreground: oklch(0.9766 0.0017 67.8025);
  /* ... Dark mode variables */
}

4.4 Update Custom Variants

If you use dark mode, add a custom variant:

@custom-variant dark (&:is(.dark *));

5. Fix Common Issues

5.1 Border Color Issues

If you encounter an error where the border-border class is not recognized, update the styles to:

@layer base {
  * {
    @apply outline-ring/50;
    border-color: hsl(var(--border));
  }
  body {
    @apply bg-background text-foreground;
  }
}

5.2 Animation Plugin Configuration

Make sure tailwindcss-animate loads correctly:

@plugin "tailwindcss-animate";

6. Test and Verify

# Test the build
npm run build

# Start the development server
npm run dev

Complete Example

package.json Dependencies

{
  "devDependencies": {
    "tailwindcss": "^4.1.12",
    "tailwindcss-animate": "^1.0.7",
    "@tailwindcss/postcss": "^4.1.12",
    "autoprefixer": "^10.4.20",
    "postcss": "^8"
  }
}

Complete globals.css

@import "tailwindcss";
@plugin "tailwindcss-animate";

@custom-variant dark (&:is(.dark *));

@theme {
  --font-sans: -apple-system, BlinkMacSystemFont, 'Noto Sans SC', system-ui, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-mono: 'SF Mono', Monaco, 'Inconsolata', 'Roboto Mono', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
}

:root {
  --radius: 0.5rem;
  --background: oklch(0.9766 0.0017 67.8025);
  --foreground: oklch(0 0 0);
  --card: oklch(1.0000 0 0);
  --card-foreground: oklch(0 0 0);
  --popover: oklch(1.0000 0 0);
  --popover-foreground: oklch(0 0 0);
  --primary: oklch(0.6606 0.0950 53.8176);
  --primary-foreground: oklch(1.0000 0 0);
  --secondary: oklch(0.9702 0 0);
  --secondary-foreground: oklch(0 0 0);
  --muted: oklch(0.9219 0 0);
  --muted-foreground: oklch(0.5555 0 0);
  --accent: oklch(0.6606 0.0950 53.8176);
  --accent-foreground: oklch(0 0 0);
  --destructive: oklch(0.5845 0.1216 34.8390);
  --destructive-foreground: oklch(1.0000 0 0);
  --border: oklch(0.9219 0 0);
  --input: oklch(0.9219 0 0);
  --ring: oklch(0.6606 0.0950 53.8176);
}

.dark {
  --background: oklch(0.2178 0 0);
  --foreground: oklch(0.9766 0.0017 67.8025);
  --card: oklch(0.2686 0 0);
  --card-foreground: oklch(0.9766 0.0017 67.8025);
  --popover: oklch(0.2686 0 0);
  --popover-foreground: oklch(0.9766 0.0017 67.8025);
  --primary: oklch(0.6606 0.0950 53.8176);
  --primary-foreground: oklch(0 0 0);
  --secondary: oklch(0.3715 0 0);
  --secondary-foreground: oklch(0.9766 0.0017 67.8025);
  --muted: oklch(0.3715 0 0);
  --muted-foreground: oklch(0.7155 0 0);
  --accent: oklch(0.6606 0.0950 53.8176);
  --accent-foreground: oklch(0 0 0);
  --destructive: oklch(0.5845 0.1216 34.8390);
  --destructive-foreground: oklch(0 0 0);
  --border: oklch(0.3715 0 0);
  --input: oklch(0.2686 0 0);
  --ring: oklch(0.6606 0.0950 53.8176);
}

@layer base {
  * {
    @apply outline-ring/50;
    border-color: hsl(var(--border));
  }
  body {
    @apply bg-background text-foreground;
  }
}

FAQ

Q: What if styles aren't loading?

A: Check that your PostCSS configuration is correct and uses the @tailwindcss/postcss plugin.

Q: Why do shadcn/ui components look wrong?

A: Make sure all color variables are mapped correctly in @theme inline.

Q: Why aren't animations working?

A: Make sure @plugin "tailwindcss-animate" is loaded correctly.

Q: Why is the build failing?

A: Check that you've deleted the old tailwind.config.js file to avoid configuration conflicts.

Performance Comparison

Build performance before and after migration:

Metric v3 v4 Speedup
Full build ~15s ~3s 5x
Incremental build ~2s ~20ms 100x
Dev server startup ~3s ~1s 3x

Summary

Migrating to Tailwind CSS v4 mainly involves:

  1. Changing the configuration approach (JS → CSS)
  2. Updating the PostCSS plugin
  3. Adjusting variable mappings

Although some manual work is required, the gains in performance and developer experience are significant. For projects targeting modern browsers, I strongly recommend upgrading to v4.


This article is based on my experience migrating a real project. If you run into issues, refer to the official Tailwind CSS v4 documentation.

Related posts

Comments 0