/

Moving shadcn Components to Tailwind v4

Configuration moved into CSS. Three sharp edges in the migration, and why a v3-era component installs and silently does nothing.

Serafim Korablev
Serafim Korablev
@korablev

Tailwind v4 moved the configuration out of JavaScript and into CSS. For a project that owns its components, that is a small migration with three sharp edges, and for anyone installing components written for v3 it is the reason a pasted theme or a copied component quietly does nothing.

What changed

The config is CSS now. tailwind.config.js is replaced by an @theme block in your stylesheet, where design tokens are declared as CSS variables. There is still an escape hatch for a JS config, but the direction is clear.

The import changed. @tailwind base; @tailwind components; @tailwind utilities; becomes a single @import "tailwindcss";.

Content detection is automatic. No more content globs for the common case. In a monorepo you still point at packages explicitly with @source, which the monorepo guide covers, and forgetting it is why a shared component renders unstyled.

Colours are in OKLCH. The default palette moved to a perceptually uniform space, which is also why current shadcn themes are written that way. The theming guide explains what that buys.

The three sharp edges

Ring width. In v3, ring meant a 3px ring. In v4 it means 1px. Every focus style in your application changes at once, and because focus rings are the thing nobody screenshots, it is easy to ship a migration where every control's focus indicator became a third of its previous weight.

Default border colour. v3 gave borders a default grey; v4 uses currentColor. Any border class without an explicit colour now inherits the text colour, which turns quiet dividers into dark lines.

Renamed utilities. The scale shifted in places, and the gradient utilities were renamed. A codemod handles most of it, and the ones it misses are usually inside strings your build cannot see: class names assembled at runtime, or utilities living in a database.

The migration, in order

Run the official upgrade tool first. It rewrites the import, converts the config into @theme, and applies the utility renames, which is most of the mechanical work.

Then, in this order: check the focus rings, since the width change is invisible until someone tabs; check every bare border class; and check anything that builds class names dynamically, because the codemod cannot see those strings.

Finally, re-run the app in both themes. The colour space change is not a visual change in principle and it does move some values in practice.

Installing v3-era components into a v4 project

This is where most people meet the migration without planning to.

A component written for v3 that only uses utility classes installs and works. A component that expects you to extend tailwind.config.js - a custom animation, a custom colour, a plugin - installs and silently does not, because there is no config for it to extend. The fix is translating that extension into your @theme block, which is usually a few lines.

The tell is a component that renders with the right structure and none of its motion or colour. Before assuming it is broken, look at whether its README asks for a config change.

Where else to look

The honest list, because the answer is not always us:

SourceBest forTrade-off
The official upgrade toolThe mechanical part of the migrationIt cannot see dynamically built class names
Tailwind v4 docsThe canonical @theme and @source referenceNot shadcn-specific
shadcn/ui docsThe component library's own v4 notesAssumes the official components
21stComponents that read the standard tokens either wayQuality varies by author, so check the version it expects

Taking one

Every component page has a live preview and the code. Installing goes through the shadcn CLI against our registry:

bash

That key comes from your 21st account, and installs require a membership. Set API_KEY_21ST once in your shell and the command works for anything in the catalogue.

Browse components →

Frequently asked

What changed between Tailwind v3 and v4?
Configuration moved from JavaScript into CSS with an @theme block, the three @tailwind directives became one import, content detection is automatic outside monorepos, and the default palette moved to OKLCH. For component work the practical consequence is that a v3-era config extension has nowhere to go.
What breaks in a Tailwind v4 migration?
Three things in particular: ring changed from 3px to 1px, so every focus indicator got lighter at once; the default border colour is now currentColor, so bare border classes inherit text colour; and dynamically built class names are invisible to the codemod, so any utility assembled at runtime needs checking by hand.
Why does a component I installed do nothing in v4?
It probably expects a tailwind.config.js extension for a custom animation, colour or plugin, and there is no config for it to extend. Translate that extension into your @theme block. The tell is a component that renders with the right structure and none of its motion or colour.
Do I need content globs in Tailwind v4?
Not for a single application, where detection is automatic. In a monorepo you still point at packages explicitly with @source, and forgetting it is the usual reason a shared component renders unstyled in the app while looking fine in isolation.

Published

Aug 20, 2026

Read time

4 min

Tags

How-toTailwindshadcnCSS

Share