If you spent any time on React Twitter or LinkedIn lately, you saw three names everywhere: shadcn/ui, Radix, and Base UI. People talk about them like they compete with each other, but they don't really. Let me explain what each one actually is, and when you should reach for which.
First, what is a "headless" UI library?
Before we compare anything, you need this idea.
A normal UI library like Bootstrap or Material UI gives you components that already look a certain way. You import a <Button> and it comes with colors, padding, hover effects, the full package. You can override the styles, but you are fighting the library.
A headless library does the opposite. It gives you the behavior of a component (open and close, keyboard navigation, focus management, accessibility) without any styling at all. You bring your own CSS. You decide how it looks.
Think of it like this: a headless library is the engine of a car. shadcn, Bootstrap, MUI are full cars. You can swap the engine, but the car already has paint.
This is important because the boring part of building a UI is not the colors. It is making a dropdown that closes when you press Esc, returns focus to the right place, traps focus in a modal, supports arrow keys in a menu, and works with screen readers. That stuff is HARD. Headless libraries solve it once so you don't have to.
Radix UI
Radix is a headless library by the team behind WorkOS (they bought Radix). It gives you primitives like <Dialog>, <DropdownMenu>, <Tabs>, and so on, fully accessible, no styles.
It became the default choice in the React ecosystem over the past few years. Vercel, Linear, Supabase, and a huge chunk of modern apps use it under the hood.
Why people love it:
- battle tested, used in millions of production apps
- great accessibility out of the box
- clean API with the
asChildpattern (more on this below)
The honest downside:
Updates have slowed down a lot since the WorkOS acquisition. Many devs feel it has reached its peak and won't get major new features. It is stable, but stable can mean "frozen".
Base UI
Base UI is the new kid, started in 2024 by people from Radix, MUI, and Floating UI (so basically the dream team of headless React). It is also a headless primitives library, very similar API to Radix, but with some upgrades.
Why people are excited:
- actively developed (7+ engineers shipping new releases regularly)
- ships components Radix doesn't have, like
ComboboxandAutocomplete - better focus on performance and tiny modern a11y wins (for example, accordions automatically open when the user does Ctrl+F to search hidden text)
- API is close enough to Radix that migrating is mostly find and replace
The honest downside:
Newer means smaller community, fewer Stack Overflow answers, and your AI assistant might not know it as well (we will fix that in the next post).
The asChild vs render difference
This is one of the few API differences worth knowing.
Radix uses asChild:
import * as Dialog from '@radix-ui/react-dialog';
<Dialog.Trigger asChild>
<button className="my-custom-button">Open</button>
</Dialog.Trigger>
The asChild tells Radix: "don't render your own element, merge your behavior into my child instead".
Base UI uses render:
import { Dialog } from '@base-ui-components/react/dialog';
<Dialog.Trigger render={<button className="my-custom-button">Open</button>}>
Open
</Dialog.Trigger>
Same idea, different shape. Base UI's render prop is a bit more flexible because you can also pass a function for full control.
shadcn/ui
Now the third name. shadcn/ui is not a headless library. It is also not really a library at all in the npm sense.
It is a collection of pre-styled, copy paste components built on top of Radix or Base UI, using Tailwind CSS. When you want a Button or Dialog, you run a CLI command and it copies the component code directly into your project.
pnpm dlx shadcn@latest add button
You now own that component. You can edit it, delete props, add variants. There is no node_modules/shadcn-ui to fight with.
Why this changed everything:
- you get gorgeous defaults that you can fully customize because the code is yours
- a11y is handled by the underlying Radix or Base UI primitive
- you don't carry the weight of components you never use
Big news from late 2025:
shadcn/ui now supports both Radix and Base UI as the underlying engine. When you start a project, you pick one. Every component was rebuilt for Base UI while keeping the same API.
So what should you actually pick?
Here is my honest junior friendly advice:
If you are starting a new project today:
Use shadcn/ui with Base UI. You get pretty defaults you control, accessibility for free, and you are betting on the library that is actively growing instead of the one that is frozen.
If you are joining an existing team:
Use whatever they already use. If it is Radix, don't migrate just because Base UI is trendy. Radix in production today is fine. It works.
If you need a component Radix doesn't have:
Like Combobox or Autocomplete, just go with Base UI. You will save yourself a week of building it from scratch.
If you want a fully styled out of the box library:
Then you don't want any of these three. Look at MUI, Mantine, or Chakra. shadcn, Radix, and Base UI all assume you are styling things yourself with Tailwind or CSS.
A simple decision tree
Need full control over styles?
├── Yes
│ └── Want a copy paste starter with nice defaults?
│ ├── Yes → shadcn/ui (pick Base UI as the engine)
│ └── No → Base UI directly (or Radix if your team uses it)
└── No → MUI, Mantine, or Chakra
Final thought
The whole "Radix vs Base UI" debate online is loud, but in practice the difference is small for a junior. Both give you accessible, headless primitives. Both work great. Base UI is the safer long term bet because the team is shipping, but you cannot go wrong with either.
The real win is just stopping yourself from building a custom modal from scratch in 2026. That is the bug.
This article was originally published by DEV Community and written by Mohamed Idris.
Read original article on DEV Community