Back

Upgrading to Tailwind v4

March 20, 2025

Dark Helmet

This was kind of a pain, but here's how I did it.

Start in clean branch. (obvi)

pnpm dlx @tailwindcss/upgrade
pnpm add clsx@latest
pnpm add tailwind-merge@latest
pnpm remove tailwind-animate
pnpm add tw-animate-css -D

Add @import 'tw-animate-css'; to globals.css after @import 'tailwindcss';

@import 'tailwindcss';
@import 'tw-animate-css';

Move fonts to @theme inline.

/* https://tailwindcss.com/docs/theme#referencing-other-variables */
@theme inline {
  --font-sans: var(--font-inter), Arial, Helvetica, sans-serif;
}

Add button CSS.

@layer base {
  button:not(:disabled),
  [role="button"]:not(:disabled) {
    cursor: pointer;
  }
}

Port your "components" from v3 config to v4 utility classes.

@utility my-display-title {
  font-weight: 600;
  font-size: 24px;
  line-height: 32px;
}

Follow shadcn's recommendation on CSS variables. Move all your CSS vars to :root and .dark respectively.

:root {
  --background: 0 0% 100%;
}

.dark {
  --background: 240 10% 3.9%;
}

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

Then wrap them in hsl() and remove hsl() from its usage.

:root {
  --background: hsl(0 0% 100%);
}

.dark {
  --background: hsl(240 10% 3.9%);
}

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

Then move the entire theme to inline because Tailwind says you have to do this to reference other variables.

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

Lastly, replace all h-* w-* with size-*. This is easy if you have prettier and prettier-plugin-tailwindcss installed. Since the Tailwind migration ran in the first step, your class names might not be in order anymore. Run prettier --write ./src and let Prettier sort everything. It will put height first, width second. Then run a regex find and replace:

# Search: h-(\d+)\s+w-\1
# Replace: size-$1

At this point you should be completely migrated to Tailwind v4 and prepped for shadcn updates. Commit into main and call it a day.

- jonkwheeler