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:
| Source | Best for | Trade-off |
|---|---|---|
| The official upgrade tool | The mechanical part of the migration | It cannot see dynamically built class names |
| Tailwind v4 docs | The canonical @theme and @source reference | Not shadcn-specific |
| shadcn/ui docs | The component library's own v4 notes | Assumes the official components |
| 21st | Components that read the standard tokens either way | Quality 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:
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.
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.