shadcn/ui Switched Defaults to Base UI — Radix Isn't Dead, but the Old Assumption Is
I was recently deciding on a tech stack for a new project.
For the UI, I naturally wrote down:
shadcn/ui (Radix UI)
Over the past few years, this has almost become a fixed pairing. When mentioning shadcn/ui, many people first think of Radix Primitives, Tailwind CSS, and a set of component code that can be copied directly into a project for modification.
But I later discovered that this notation is no longer entirely accurate.
In July 2026, shadcn/ui set Base UI as the default underlying component library for new projects. The component documentation now displays the Base UI version by default, and running the initialization command no longer generates the familiar Radix version by default.
When I saw this change, my first reaction was similar to many people's:
Is shadcn/ui planning to abandon Radix?
After all, when a project changes its default option, it usually means it believes the new option is more suitable for the future. Coupled with the fact that what Base UI and Radix do seems very similar, it's easy to interpret this as a replacement.
I went through the official announcement and the documentation for both sides, and ultimately found that things are not that simple.
Changing the Default Does Not Mean Radix Is Deprecated
This change only affects the default choice for new projects.
If you still want to use Radix, you can explicitly specify it during initialization:
pnpm dlx shadcn init -b radix
Projects already using Radix will not be automatically switched. When adding components later, shadcn will still generate the corresponding code based on the project's original underlying implementation.
The official stance on this is very clear: Radix is still fully supported, and existing projects have no need to migrate just to follow the default. shadcn itself also has production projects based on Radix and has no plans to convert all these projects to Base UI.
This is very important to me.
Because shadcn/ui is not an ordinary black-box component dependency. It places the component source code into the project's components/ui, and developers usually continue to modify styles, add properties, and even adjust component structures. Once a project has undergone these customizations, so-called "migrating the underlying component library" is never as simple as changing package.json.
Common Radix patterns like asChild, data-state, CSS variables, and event behaviors may have already entered the project's own component code. Rewriting all this code just because the default for new projects changed is hard to justify by the benefits.
So my current judgment is simple:
Old projects should keep running as they are. The change in defaults is mainly for new projects to see.
Why Base UI Could Become the Default
Since Radix hasn't had any serious issues, why did shadcn still put Base UI first?
A few numbers from the official announcement are quite interesting.
At the time of the announcement, Base UI had already reached version 1.6.0, with over 6 million weekly downloads. The shadcn team's newly started projects were all using Base UI; in shadcn/create, which previously allowed users to freely choose, the ratio of new projects choosing Base UI versus Radix was about 2:1.
In other words, shadcn didn't suddenly decide to push a new solution that no one was using. It first allowed the two implementations to coexist for a period, and only adjusted the default after its own new projects and new projects in the community clearly favored Base UI.
What I care more about is not the download numbers, but which components Base UI has been adding recently.
Radix's Dialog, Popover, Dropdown Menu, Tooltip, and Select are already very mature. These were also its most attractive features in the past. But now, building a complete SaaS, AI product, or admin dashboard often involves interactions beyond just a set of dialogs and menus.
Combobox, Autocomplete, Number Field, Drawer, OTP Field, and more complete Field and Form components appear frequently. Base UI's coverage of these modern application components is more proactive, and its update speed is faster.
This doesn't prove that Radix's components are not good enough. A more accurate statement is:
Radix's advantage is that its classic components are already sufficiently mature; Base UI's advantage is that it is still rapidly expanding outward.
For a default option, shadcn needs to consider not only which components are stable today, but also whether the underlying library can provide corresponding capabilities in time when new components are added in the future.
Their Differences Are More Than Just a Name Change
Base UI is not a competitor that appeared out of thin air.
Its team background has connections with Radix, Material UI, and Floating UI, and its overall philosophy is very similar: the components themselves do not prescribe a visual style, but are responsible for accessibility, focus management, keyboard operations, and complex interaction states, upon which developers build their own design systems.
So I prefer to understand Base UI as an opportunity to redesign with existing experience, rather than a denial of Radix's path.
One of the most intuitive differences between the two is the component composition method.
Radix commonly uses asChild. DialogTrigger does not render an extra button but delegates the trigger behavior to the inner Button:
<DialogTrigger asChild>
<Button>Open Settings</Button>
</DialogTrigger>
Base UI's corresponding approach is render, where the element to replace is written directly in the prop:
<DialogTrigger render={<Button />}>
Open Settings
</DialogTrigger>
I initially thought this was just swapping one prop for another. But Base UI's render can also use a function form to directly read the component's current state:
<Switch.Thumb
className={(state) =>
state.checked ? "translate-x-5" : "translate-x-0"
}
/>
The way state styles are written is also different. In Radix components, you often see:
className="data-[state=open]:animate-in"
In Base UI, it's more common to see:
className="data-open:animate-in"
These code snippets are written directly in the article, making them easier to read than a screenshot of code and convenient for readers to copy.
Of course, these differences are not yet significant enough to warrant an immediate migration for typical projects. If the business code mainly uses shadcn's wrapped Dialog, Select, and Button, the user experience of the two implementations is actually very similar. The differences in the underlying API only become apparent when modifying components/ui, developing complex components, or building your own Registry.
What Really Changed Is shadcn/ui's Definition of Itself
After looking into all this, I think what's most noteworthy is not who won between Base UI and Radix.
What has really changed is shadcn/ui itself.
Previously, I understood it as a layer of component code and styling on top of Radix. This understanding wasn't a big problem in the early days, because the vast majority of components were indeed built on Radix.
But now, shadcn/ui simultaneously offers Base UI, Radix, and later added a React Aria version. It is trying as much as possible to let these different underlying implementations share similar component names, CLI, Registry, Blocks, and usage patterns.
This means what shadcn/ui wants to stabilize is no longer a specific Primitive library, but its own component distribution system.
The underlying layer can be Base UI, Radix, or React Aria; what developers ultimately get is still a copy of component source code placed in their own project, ready to be further modified.
So, rather than saying shadcn/ui has abandoned Radix, it's more accurate to say it is shedding another, older impression:
shadcn/ui equals Radix UI.
This is also why I will no longer just write shadcn/ui and assume everyone knows what the project's underlying layer is.
In tech stack documentation, it's best to explicitly write:
shadcn/ui (Base UI)
or:
shadcn/ui (Radix UI)
For AI-Assisted Development, This Is Actually More Troublesome
I now have AI participate in the implementation of many projects, so this change has a very practical impact.
AI can easily mix shadcn code from different periods.
It might generate Base UI components on one hand while continuing to write Radix patterns:
asChild
data-[state=open]
--radix-popover-content-available-height
Or it might suddenly insert into an originally Radix project:
render={<Button />}
data-open
These code snippets might be correct individually, but when placed in the wrong project, they lead to type errors, broken state styles, or component behavior that doesn't match the documentation.
Previously, just writing "using shadcn/ui" might have been enough. Now, it's best to also write the underlying implementation into the PRD, technical architecture, and execution prompts.
For example, a new project can explicitly require:
UI uses shadcn/ui, with Base UI as the underlying layer.
Do not generate Radix-specific asChild, data-state, or Radix CSS variables.
For an old project, the opposite:
Current project uses shadcn/ui + Radix UI.
Maintain existing Radix APIs, do not actively migrate to Base UI.
This might be more useful than debating which library is more advanced. Because the real problem most projects encounter is not making the wrong choice, but having another set of patterns mixed in after the choice was made.
How I Will Ultimately Choose
Now, when starting a new project, I will directly use Base UI. Not because Radix is unusable, but because shadcn/ui has already set it as the default, making the documentation and subsequent components more convenient.
For projects already using Radix, I won't touch them. If they run fine, there's no need to migrate just to change the default.
From now on, I will only write clearly in the tech stack: shadcn/ui (Base UI), or shadcn/ui (Radix UI). This way, AI is less likely to mix the two sets of APIs when writing code.