6 min read

Radix UI vs Base UI for shadcn/ui: Start New Apps on Base UI, Keep Radix Where It Ships

Mehdi Rezaei
Mehdi
Author
Engineering
Software
Technology

For a new Next.js app built with shadcn/ui in late 2026, start on Base UI. Since July 2026 npx shadcn init picks it by default, and it has components Radix never shipped, such as Combobox, Autocomplete, and Number Field. For an app that already runs on Radix and passes its tests, keep Radix. shadcn has not deprecated it, every new shadcn component still ships for both libraries, and the shadcn team still runs Radix in production. Switch an existing app only when a Base UI component removes custom code you maintain today.

The question lands on my desk in two forms. A founder asks if the new project should "use the new thing." A team lead asks if the old project is now legacy. Both answers come down to what you have already customized.

What changed in 2026

shadcn/ui launched on Radix in January 2023.

Base UI comes from MUI with core contributors from Radix and Floating UI, and it reached a stable 1.0 on December 11, 2025, with 35 unstyled components and the new @base-ui/react package. In December 2025 shadcn added npx shadcn create with a choice of either library. Full Base UI docs followed in January 2026.

In July 2026 shadcn made Base UI the default for new projects. The changelog gives four reasons: Base UI was stable at 1.6.0, it ships new components often, the shadcn team starts its own projects on it, and users on shadcn/create were already picking Base UI over Radix two to one.

The npm numbers explain why nobody should panic either way. For the week of September 28 to October 4, 2026, @base-ui/react logged about 19.3 million downloads and the radix-ui umbrella package about 19.1 million. A single scoped Radix package, @radix-ui/react-dialog, logged about 91.9 million, because years of apps depend on it. Radix still ships too: WorkOS maintains the primitives repo, and radix-ui 1.7.0 is the current release on npm.

Where the two APIs differ

shadcn keeps the wrapper API the same. You still import Dialog, DialogTrigger, and DialogContent from @/components/ui/dialog. The differences show up in the props your app code passes. You will hit these first:

  • Composition: Radix uses asChild to swap the rendered element. Base UI uses a render prop.
  • Non-button triggers: when Base UI renders a trigger as a link or span, you add nativeButton={false}.
  • Select: Base UI wants an items prop on the root, puts the placeholder in a { value: null } item, and supports multiple and object values. Radix Select takes a single string value.
  • Accordion and ToggleGroup: Radix uses type="single" or type="multiple". Base UI uses a multiple boolean, and defaultValue is always an array.
  • Slider: Base UI accepts a plain number for one thumb. Radix always wants an array.

The composition change is the one that touches the most files:

TypeScript
1// Radix
2<DialogTrigger asChild>
3 <Button>Open</Button>
4</DialogTrigger>
5
6// Base UI
7<DialogTrigger render={<Button />}>Open</DialogTrigger>

None of these are hard. They add up across a product, and a search and replace on asChild will not catch a Select that relied on string values or an Accordion that relied on collapsible.

Pick Base UI for a new project when

  • You need a searchable select or tag input. Base UI has Combobox and Autocomplete. On Radix, shadcn builds that pattern from cmdk inside a Popover, and you end up owning the glue.
  • Forms are the product. Think number inputs, OTP codes, grouped checkboxes, and validation per field. Base UI ships Number Field, OTP Field, Checkbox Group, Field, Fieldset, and Form.
  • Mobile matters. Base UI has a Drawer with swipe-to-dismiss, stable since 1.3.0.
  • You want the path the shadcn docs and examples now open on first. Registry items without a registry:base setting now init as Base UI.

Keep Radix when

  • The app works and the shadcn wrappers are heavily customized. shadcn's own advice: "If your app works, keep shipping."
  • You depend on community registries or internal kits written against Radix props. Mixing both libraries works, but two mental models in one codebase cost review time.
  • Your CI runs shadcn init non-interactively. Add -b radix, or a fresh scaffold will switch libraries under you.
  • The release calendar is tight. A primitive swap is a UI regression risk with no user-facing feature attached.

If you do migrate, do it one component at a time

shadcn chose a skill over a codemod for a reason. You own the wrapper code, and a codemod breaks on files you changed. Install it with pnpm dlx skills add shadcn/ui, then ask your coding agent to "migrate accordion to base-ui." It migrates one component and its call sites at a time, writes a report per component under .migration/, and commits each component separately on a branch. On their test projects, 60 or more components with 36 on Radix, a full run took about 25 minutes at about 10,000 tokens per component.

My checklist around that:

  1. Start from a clean tree and a green build. Run typecheck and build first, so old failures are not blamed on the migration.
  2. Install @base-ui/react next to Radix. Both coexist until the last component moves.
  3. Move leaf components first (Tooltip, Popover, Dialog), then Select and Accordion, where behavior differs.
  4. Read the "Behavior changes" section of each report. Those compile fine and still act differently.
  5. Run your end-to-end suite and tab through each migrated screen with a keyboard before you merge.

The decision in one line

New shadcn/ui project: Base UI. Healthy Radix project: stay, and revisit when a Base UI component deletes code you maintain. If your design system rests on a customized shadcn kit, read the composition and Select differences before anyone schedules a "quick" swap. For the wider question of how to structure that kit, see my post on building a design system with Tailwind and shadcn/ui.

Sources

Share this article