shadcnthemes
All guides

Migrate a shadcn/ui theme from Tailwind v3 to v4

Convert a Tailwind v3 shadcn/ui theme (HSL channels in globals.css plus tailwind.config.js) to Tailwind v4's OKLCH variables and @theme inline, step by step.

Updated October 24, 2026

Tailwind CSS v4 changed how shadcn/ui themes are written. If your project still has bare HSL channels like --primary: 222.2 47.4% 11.2% and a tailwind.config.js full of hsl(var(--primary)), here's how to bring the theme across.

What changed

Tailwind v3Tailwind v4
Colors stored as bare HSL channels: --primary: 222.2 47.4% 11.2%Complete color values, usually OKLCH: --primary: oklch(0.21 0.034 264.7)
Colors mapped in tailwind.config.js with hsl(var(--primary))Colors mapped in CSS with @theme inline { --color-primary: var(--primary); }
darkMode: "class" in the config@custom-variant dark (&:is(.dark *)); in CSS
Radius mapped in the config--radius-lg: var(--radius) and friends in @theme inline
No chart or sidebar tokens in older projectschart-1…chart-5 and eight sidebar-* tokens

Step 1: Convert the color values

Every --token: H S% L% needs to become a full color. You can wrap the channels as hsl(H S% L%), which works, but converting to OKLCH matches what new shadcn projects use and makes later tweaks easier.

The fastest route is the Tailwind v3 → v4 theme converter: paste your entire v3 :root and .dark blocks. It understands bare HSL channels, converts everything to OKLCH, fills in missing chart and sidebar tokens from your primary color, and gives you Tailwind v4 CSS to copy. You can also paste the same CSS into the editor's Import dialog to keep editing.

Convert your theme
Paste Tailwind v3 CSS, get Tailwind v4 OKLCH CSS. Runs in your browser.

Step 2: Replace the config mapping with @theme inline

Delete the colors and borderRadius sections from tailwind.config.js; in v4 that config file is usually gone entirely. Add the mapping to your CSS instead:

@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-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);
}

Add the chart-* and sidebar-* mappings too if you use charts or the sidebar component.

Step 3: Move the dark-mode variant into CSS

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

next-themes and the dark class on <html> keep working exactly as before.

Step 4: Update the base layer

The v3 base layer used @apply border-border and bg-background text-foreground. Those still work in v4:

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Common gotchas

  • Arbitrary values that wrap variables: classes like bg-[hsl(var(--primary))] break once --primary holds a full color. Replace them with bg-primary or bg-(--primary).
  • Opacity modifiers: bg-primary/50 keeps working in v4 because Tailwind uses color-mix(). No more hsl(var(--primary) / 0.5) gymnastics.
  • Third-party CSS that reads hsl(var(--background)) needs the same update.

Prefer to start over? Pick any theme from the gallery. They're all native v4 and install with one CLI command.

More guides