Migrating from Tailwind 3 to Tailwind 4 with shadcn/ui

13 min readJan 4, 2026

Morgan Feeney

Digital craftsman

This is a step by step guide on how to migrate from Tailwind CSS 3 to Tailwind CSS 4 with shadcn/ui

Tailwind CSS 4 represents a complete rewrite of the framework with massive performance improvements, a CSS-first configuration system, and full support for modern browser features. If you're using shadcn/ui components, this guide will walk you through the migration process based on the official documentation.

What's New in Tailwind CSS 4

Tailwind CSS v4.0 is a ground-up rewrite with a new high-performance engine that makes full builds over 3.5x faster and incremental builds over 8x faster. Incremental builds that don't need to compile new CSS are over 100x faster and complete in microseconds.

Key changes include:

  • CSS-first configuration: Configuration now lives in CSS using @import and @theme instead of JavaScript
  • Automatic content detection: No more configuring the content array - Tailwind automatically discovers your template files
  • Built-in imports: Native @import support without needing PostCSS plugins
  • Modern CSS features: Built on cascade layers, @property, and color-mix()
  • Simplified dependencies: Fewer dependencies and zero configuration needed
  • First-party Vite plugin: Tight integration for maximum performance

Prerequisites

Important: Tailwind CSS v4.0 is designed for Safari 16.4+, Chrome 111+, and Firefox 128+. If you need to support older browsers, stick with v3.4 until your browser support requirements change.

Before migrating, ensure you have:

  • Node.js 20 or higher (required for the upgrade tool)
  • A project using Tailwind CSS 3.x and shadcn/ui
  • Modern browser support requirements
  • Your project committed to version control

Step 1: Use the Official Upgrade Tool

Tailwind provides an automated upgrade tool that handles most of the migration process including updating dependencies, migrating configuration to CSS, and updating template files.

Run the upgrade tool:

npx @tailwindcss/upgrade

Important: Run the upgrade tool in a new branch, then carefully review the diff and test your project in the browser to make sure all changes look correct.

Step 2: Update CSS Import Syntax

The biggest change is how you import Tailwind. In v4 you import Tailwind using a regular CSS @import statement, not using the @tailwind directives you used in v3.

Before (Tailwind v3):

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

After (Tailwind v4):

@import "tailwindcss";

That's it! Just one line.

Step 3: Migrate Configuration to CSS

Instead of a tailwind.config.js file, you can configure all of your customizations directly in the CSS file where you import Tailwind.

Before (tailwind.config.js):

module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#3b82f6',
        secondary: '#8b5cf6',
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif'],
      },
    },
  },
}

After (in your CSS file):

@import "tailwindcss";

@theme {
  --color-primary: #3b82f6;
  --color-secondary: #8b5cf6;
  --font-sans: Inter, sans-serif;
}

Step 4: Update shadcn/ui CSS Variables

The upgrade tool will migrate your CSS variables, but you need to optimize them for easier use. Here's the shadcn/ui-specific approach:

The codemod will migrate your CSS variables as references under the @theme directive, but to make it easier to work with colors and other variables, you'll need to move the hsl wrappers and use @theme inline.

After running the upgrade tool, you'll have:

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 0 0% 3.9%;
  }
}

@theme {
  --color-background: hsl(var(--background));
  --color-foreground: hsl(var(--foreground));
}

Optimize it to:

:root {
  --background: hsl(0 0% 100%);
  --foreground: hsl(0 0% 3.9%);
  --card: hsl(0 0% 100%);
  --card-foreground: hsl(0 0% 3.9%);
  --popover: hsl(0 0% 100%);
  --popover-foreground: hsl(0 0% 3.9%);
  --primary: hsl(222.2 47.4% 11.2%);
  --primary-foreground: hsl(210 40% 98%);
  --secondary: hsl(210 40% 96.1%);
  --secondary-foreground: hsl(222.2 47.4% 11.2%);
  --muted: hsl(210 40% 96.1%);
  --muted-foreground: hsl(215.4 16.3% 46.9%);
  --accent: hsl(210 40% 96.1%);
  --accent-foreground: hsl(222.2 47.4% 11.2%);
  --destructive: hsl(0 84.2% 60.2%);
  --destructive-foreground: hsl(210 40% 98%);
  --border: hsl(214.3 31.8% 91.4%);
  --input: hsl(214.3 31.8% 91.4%);
  --ring: hsl(222.2 84% 4.9%);
  --radius: 0.5rem;
  --chart-1: hsl(12 76% 61%);
  --chart-2: hsl(173 58% 39%);
  --chart-3: hsl(197 37% 24%);
  --chart-4: hsl(43 74% 66%);
  --chart-5: hsl(27 87% 67%);
}

.dark {
  --background: hsl(222.2 84% 4.9%);
  --foreground: hsl(210 40% 98%);
  /* ... rest of dark mode variables */
}

@theme inline {
  --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);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  --radius: var(--radius);
}

The key changes:

  1. Move :root and .dark out of @layer base
  2. Wrap color values in hsl() in the CSS variables, or the colour function of your choosing, e.g. oklch()
  3. Use @theme inline instead of just @theme

Step 5: Update Build Configuration

For Vite Projects

If you're using Vite, migrate from the PostCSS plugin to the new dedicated Vite plugin for improved performance and the best developer experience.

import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'

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

Unless you have additional configurations, you can now remove your postcss.config.js file - you don't need it anymore.

For Next.js Projects

Update your postcss.config.js:

module.exports = {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}

Using Tailwind CLI

In v4, Tailwind CLI lives in a dedicated @tailwindcss/cli package.

Update your package.json scripts:

{
  "scripts": {
    "build:css": "tailwindcss -i input.css -o output.css"
  }
}

Install the new CLI package:

npm install -D @tailwindcss/cli

Step 6: Update Chart Colors

Now that the theme colors come with hsl(), you can remove the wrapper in your chartConfig.

Before:

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "hsl(var(--chart-1))",
  },
  mobile: {
    label: "Mobile",
    color: "hsl(var(--chart-2))",
  },
} satisfies ChartConfig

After:

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "var(--chart-1)",
  },
  mobile: {
    label: "Mobile",
    color: "var(--chart-2)",
  },
} satisfies ChartConfig

Step 7: Use the New size-* Utility

The new size-* utility added in Tailwind v3.4 is now fully supported by tailwind-merge. You can replace width and height pairs:

// Before
<div className="w-4 h-4" />

// After
<div className="size-4" />

Step 8: Update Component Dependencies

Update your dependencies to ensure compatibility with Tailwind v4 and React 19.

pnpm up "@radix-ui/*" cmdk lucide-react recharts tailwind-merge clsx --latest

Or with npm:

npm update "@radix-ui/*" cmdk lucide-react recharts tailwind-merge clsx --latest

Step 9: Remove forwardRef

Tailwind v4 and React 19 remove the need for forwardRef in components. You can use the remove-forward-ref codemod to migrate your forwardRef to props or manually update the primitives.

You can use the React codemod:

npx react-codemod@latest remove-forward-ref ./components

Or manually update each component:

Before:

const AccordionItem = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
  <AccordionPrimitive.Item
    ref={ref}
    className={cn("border-b", className)}
    {...props}
  />
))
AccordionItem.displayName = "AccordionItem"

After:

function AccordionItem({
  className,
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
  return (
    <AccordionPrimitive.Item
      data-slot="accordion-item"
      className={cn("border-b", className)}
      {...props}
    />
  )
}

Note the addition of data-slot attribute for styling purposes.

Step 10: Update Animation Plugin

shadcn/ui has deprecated tailwindcss-animate in favor of tw-animate-css.

  1. Remove tailwindcss-animate from your dependencies
  2. Install tw-animate-css:
npm install -D tw-animate-css
  1. Update your CSS file:
/* Before */
@plugin 'tailwindcss-animate';

/* After */
@import "tw-animate-css";

Renamed Utilities

Tailwind v4 renamed several utilities to make them more consistent and predictable. Here are all the changes:

v3v4
shadow-smshadow-xs
shadowshadow-sm
drop-shadow-smdrop-shadow-xs
drop-shadowdrop-shadow-sm
blur-smblur-xs
blurblur-sm
backdrop-blur-smbackdrop-blur-xs
backdrop-blurbackdrop-blur-sm
rounded-smrounded-xs
roundedrounded-sm
outline-noneoutline-hidden
ringring-3

Updated shadow, radius, and blur scales: The "bare" versions still work for backward compatibility, but the <utility>-sm utilities will look different unless updated to their respective <utility>-xs versions.

Renamed outline utility: The outline-none utility didn't actually set outline-style: none - it set an invisible outline for accessibility. It's now renamed to outline-hidden, and a new outline-none utility actually sets outline-style: none.

Default ring width change: The ring utility now adds a 1px ring instead of 3px to be consistent with borders and outlines. Replace ring with ring-3 to maintain the old behavior.

Removed Deprecated Utilities

Tailwind v4 removes utilities that were deprecated in v3 and have been undocumented for several years. Here's the complete list:

DeprecatedReplacement
bg-opacity-*Use opacity modifiers like bg-black/50
text-opacity-*Use opacity modifiers like text-black/50
border-opacity-*Use opacity modifiers like border-black/50
divide-opacity-*Use opacity modifiers like divide-black/50
ring-opacity-*Use opacity modifiers like ring-black/50
placeholder-opacity-*Use opacity modifiers like placeholder-black/50
flex-shrink-*shrink-*
flex-grow-*grow-*
overflow-ellipsistext-ellipsis
decoration-slicebox-decoration-slice
decoration-clonebox-decoration-clone

The upgrade tool should handle most of these automatically.

Automatic Content Detection

One of the best improvements: Tailwind v4 came up with a bunch of heuristics for detecting all of your template files automatically so you don't have to configure the content array at all. It automatically ignores anything in your .gitignore file and all binary extensions.

You can still explicitly add sources if needed:

@import "tailwindcss";

@source "../../packages/*/src/**/*.tsx";

Common Issues and Solutions

Issue: "Cannot find module 'tailwindcss'"

Solution: Make sure you've installed the correct v4 package:

npm install -D tailwindcss@next

Issue: Colors not working properly

Solution: Ensure you've followed the CSS variable migration steps correctly, wrapping values in hsl() and using @theme inline.

Issue: Build is slower than expected

Solution: If using Vite, make sure you're using the @tailwindcss/vite plugin instead of the PostCSS plugin.

Issue: Components look different after migration

Solution: shadcn/ui updated dark mode colors to be more accessible. You may need to review and adjust your color scheme.

Testing Your Migration

After migration, thoroughly test:

  1. All component variants: Buttons, forms, dialogs, etc.
  2. Dark mode: Ensure theme switching works correctly
  3. Responsive design: Test all breakpoints
  4. Custom components: Any components you've modified
  5. Build times: Confirm you're seeing performance improvements
  6. Browser compatibility: Test in your target browsers

Performance Benefits

Tailwind CSS v4.0 is a ground-up rewrite optimized for speed. Based on benchmarks against real projects, you should see:

  • Full rebuilds: Over 3.5x faster (378ms → 100ms in official benchmarks)
  • Incremental rebuilds with new CSS: Over 8x faster (44ms → 5ms)
  • Incremental rebuilds with no new CSS: Over 100x faster, completing in microseconds (35ms → 192µs = 182x improvement)

The most impressive improvement is on incremental builds that don't need to compile new CSS. These builds complete in microseconds because you're reusing classes you've already used before, like flex, col-span-2, or font-bold.

Additional benefits include:

  • Smaller CSS output
  • Faster hot module replacement
  • Better overall developer experience with near-instantaneous feedback

Using shadcn/ui CLI After Migration

The CLI can now initialize projects with Tailwind v4, and when you add new components, they'll be in the latest version.

For new projects:

npx shadcn@latest init

For adding components:

npx shadcn@latest add button

New Projects vs Upgrades

Important distinction: This is non-breaking. Your existing apps with Tailwind v3 and React 18 will still work. When you add new components, they'll still be in v3 and React 18 until you upgrade.

New projects automatically start with Tailwind v4 and React 19.

Additional Resources

Conclusion

Migrating from Tailwind CSS 3 to 4 with shadcn/ui is straightforward thanks to the official upgrade tool. The CSS-first configuration takes some getting used to, but the performance improvements are significant. The combination of Tailwind v4's speed, React 19's improvements, and shadcn/ui's beautiful components provides an excellent foundation for modern web applications.

Take your time with the migration, test thoroughly, and enjoy the performance benefits!