12k
All articles

shadcn/ui Has Switched From Radix to Base UI

shadcn/ui switched new projects from Radix to Base UI. See what changed, who needs no migration, and the real costs of moving.

OpenReplay Team
OpenReplay Team
shadcn/ui Has Switched From Radix to Base UI

If your shadcn/ui app runs on Radix, you do not need to migrate. Base UI is now the default for new projects, but Radix stays fully supported, updates ship for both libraries, and the shadcn team still runs Radix in its own production apps.

The switch landed on 3 July 2026. Much of the coverage since has implied that a rewrite is coming, which is not what the changelog says.

Two questions decide what you do next: whether you have to act, and what a migration costs if you choose one. The cost splits into changes the compiler catches and changes it does not, and the second is where the risk lives.

Key Takeaways

  • Base UI is the default for new shadcn/ui projects; Radix is not deprecated, and -b radix keeps Radix on a fresh init.
  • Existing Radix apps need no changes: updates ship for both libraries, and the shadcn team still runs Radix in production.
  • The default moved because Base UI had reached a stable 1.6.0 with over six million weekly downloads, and shadcn/create users were picking it over Radix two to one.
  • A migration splits into build-breaking changes (asChild to render, Positioner/Popup, nullable Select values) and silent behavior changes (manual tab activation, menus staying open), which carry the real cost.
  • If you migrate, keep both libraries installed, move one component per commit, and use the official shadcn skill rather than a codemod.

What Changed on 3 July 2026

New shadcn/ui projects now default to Base UI, and nothing else about an existing project changed. Three things moved. Running npx shadcn init selects Base UI unless you say otherwise, shadcn/create puts Base UI at the top of its list, and the component documentation now lands you on the Base UI tab, with a Radix tab sitting next to it. Radix itself is not deprecated.

Keeping Radix on a new project takes one flag:

pnpm dlx shadcn init -b radix

Anywhere CI or a scaffolding script calls shadcn init without a prompt and assumes it will get Radix, pass that flag now. The default underneath those scripts has changed.

Do You Need to Migrate to shadcn Base UI Components?

No. The changelog commits to shipping every update and every new component on both libraries, the one exception being components that exist in Base UI and have no Radix counterpart. It also says the team’s own production code is still on Radix, with no plan to move it. A stable Radix app has no forced timeline, no deprecation window, and no maintenance cliff. The rest of this article is for teams choosing to move, not teams that must.

Why Did the Default Move?

The changelog gives four reasons. The library had reached a stable 1.6.0 and was clearing six million downloads a week. Its maintainers keep adding useful primitives. The shadcn team had already standardised on it for anything new. And among people scaffolding projects with shadcn/create, Base UI was winning roughly two picks for every one that went to Radix.

Those figures are the ones quoted in the changelog. Base UI has kept shipping since: 1.8.0 landed on 4 September 2026. The changelog also points out that Base UI comes from the same people who built Radix. The package to install is @base-ui/react; the older @base-ui-components/react name carries a deprecation notice on npm redirecting to it.

What a Migration Actually Changes

The build-breaking half of a migration is mechanical: renames and type changes the compiler catches immediately.

RadixBase UI
asChild on triggersrender prop
Portal > ContentPortal > Positioner > Popup
OverlayBackdrop
data-[state=open]: variantsdata-open: variants
onOpenChange(open) + event.preventDefault()onOpenChange(open, details) + details.cancel()

One nuance: the Positioner/Popup split applies to anchored popups such as Menu, Select, Popover, and Tooltip. Dialog has no Positioner; it is Dialog.Backdrop plus Dialog.Popup.

The callback change looks like this:

// Radix: block closing via the event
onEscapeKeyDown={(event) => event.preventDefault()}

// Base UI: one callback, with a reason and a cancel()
onOpenChange={(open, details) => {
  if (details.reason === "escape-key") {
    details.cancel()
    return
  }
  setOpen(open)
}}

Value types shift too. A controlled Select now yields Value | null rather than a string, which makes this the most common build breakage:

const [fruit, setFruit] = useState<string | null>(null)

Accordion and Toggle Group values are always arrays, even in single-selection mode, so value="a" becomes value={["a"]}.

The Changes That Compile but Behave Differently

The dangerous half of the migration type-checks cleanly and changes runtime behavior anyway. Three deltas stand out:

  • Tabs activate manually by default. Arrow keys move focus between triggers without switching the visible panel unless you set activateOnFocus on Tabs.List, which defaults to false. Radix defaulted to automatic activation.
  • Menu checkbox and radio items keep the menu open. closeOnClick defaults to false on Menu.CheckboxItem and Menu.RadioItem, the opposite of Radix’s close-on-select. Plain Menu.Item still defaults to true, so the behavior differs by item type.
  • NavigationMenu opens faster on hover. Base UI’s delay defaults to 50ms; Radix’s delayDuration defaults to 200ms. Menus users grazed past before now open.

None of this produces an error. A menu that stays open after a click and tabs that ignore arrow keys are invisible to error monitoring, because nothing throws. Session replay is where this class of regression surfaces: you watch a user press an arrow key, or click a checkbox item twice, and see the UI not respond the way they expect.

Who Should Move to Base UI and Who Should Not?

Migrate if you need Combobox, Autocomplete, or Number Field, which Radix never shipped, or if you want to stay aligned with new registry components as they land. Stay on Radix if your app is stable and none of that applies; the support commitment is explicit, and a working production app gains nothing from a primitive swap.

Do not base the decision on rumored component gaps. Base UI ships Context Menu, Toast, and a hover-card primitive under the name Preview Card, and shadcn’s Base UI docs cover all three.

How to Migrate If You Do

Use the official skill, not a codemod, and move progressively. The reasoning holds up. A codemod only knows the stock version of a file, so it copes with the components you left alone and falls over on the ones you altered. The skill reads what you actually have, brings your edits across, and reports behavior differences rather than quietly rewriting them.

pnpm dlx skills add shadcn/ui

Then ask your coding agent to migrate one component, for example migrate accordion to base-ui. You can keep both libraries installed at once, so the build stays green between steps. Each run type-checks what it produced, writes a note on that component into a .migration/ folder, and lands it as its own commit on a branch you can throw away. Start with leaf components like button and label before the components that import them, and read each report’s behavior-changes section before merging.

One warning: some guides suggest repointing components.json at Base UI and re-adding every component with --overwrite. That discards every local edit you have made to those files, which is precisely the failure the skill exists to avoid.

Where This Leaves You

The default changed; your obligations did not. Existing Radix apps keep shipping, new projects get Base UI unless you pass -b radix, and a migration is an opt-in project whose visible cost is renames and whose hidden cost is behavior. If you do migrate, budget the QA time for the silent deltas, because that is where the compiler stops helping. Start by reading the changelog entry yourself, then decide whether Combobox or registry alignment is worth the branch.

FAQs

Is Base UI production-ready compared to Radix?

Yes. Base UI shipped a stable 1.0.0 in December 2025 under the '@base-ui/react' package and has released steadily since, reaching 1.8.0 on 4 September 2026. It comes from the creators of Radix, Floating UI and Material UI, and its team says it shaped the API to resemble Radix on purpose, so that moving between the two takes less work. The shadcn team also uses Base UI for every new project it starts.

Are all value props arrays in Base UI?

No. Accordion 'value' is always an array and Toggle Group 'value' is always a string array, even in single-selection mode, but Tabs 'value' stays a single value with a default of 0, and Select is typed as a single value, an array, or null. Wrap Accordion and Toggle Group values in arrays, type controlled Select state as nullable, and leave Tabs unchanged.

Does migrating to Base UI affect shadcn components that never used Radix?

No. Command wraps cmdk, Sonner is a standalone toast library, Calendar uses react-day-picker, Input OTP uses input-otp, and Charts build on recharts. None of these depend on Radix primitives, so a Radix-to-Base-UI migration leaves them untouched. Only components whose primitives came from Radix, such as Dialog, Menu, Select, Tabs, and Popover, need any changes.

Does the shadcn migration skill require pnpm?

No. The changelog documents the skill install for pnpm, npm, yarn, and bun, so 'npx skills add shadcn/ui' is a valid npm-based equivalent of the pnpm command. After installation the skill runs through your coding agent, which migrates one component at a time and produces typechecked code, a per-component report in the '.migration' directory, and one commit per component regardless of package manager.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.