shadcn/ui vs Base UI vs Radix: Components in 2026
The React component ecosystem in 2026 looks nothing like it did three years ago. shadcn/ui went from "interesting experiment" to the default choice for new React projects, collecting 75,000+ GitHub stars in the process. Radix UI — the primitive layer that shadcn originally built on top of — has slowed down since WorkOS acquired it. And Base UI emerged from the MUI team as a serious contender that addresses Radix's architectural shortcomings with production backing from the world's most-downloaded React component library.
The three aren't directly competing. They represent different layers of the component stack, and understanding the relationship between them changes which one you should actually install.
TL;DR
For most new React projects: shadcn/ui — it's the industry default for good reason, the February 2026 Visual Builder reduces setup friction to near zero, and it now supports both Radix and Base UI as the underlying primitive layer. For custom design systems that need unstyled primitives: Base UI — it's better-maintained than Radix and has cleaner APIs for complex interactions (comboboxes, multi-select, nested menus). For existing Radix-based projects: keep Radix unless you have specific pain points — migration isn't worth the disruption for things that work.
Key Takeaways
- shadcn/ui hit 75,000+ stars — GitHub's most-starred React UI project for the third consecutive year
- shadcn/ui now supports Base UI primitives in addition to Radix — you can choose your primitive layer
- The February 2026 Visual Builder lets you configure components visually and copies the exact code — no more manual Tailwind class customization
- Radix UI was acquired by WorkOS — updates have slowed, particularly for complex components like Combobox and multi-select
- Base UI is MUI-backed with dedicated full-time engineering, not a side project
- Base UI has better TypeScript types and cleaner APIs for complex interaction patterns
- 130M monthly npm downloads for Radix — it's not going anywhere, but active development is slower
- "Headless UI" is the wrong mental model for shadcn/ui — it ships with styles, but you own them
At a Glance
| shadcn/ui | Base UI | Radix UI | |
|---|---|---|---|
| GitHub stars | 75,000+ | 4,200+ | 18,700+ |
| npm downloads/month | ~20M | ~5M | ~130M |
| Styling approach | Tailwind (you own the code) | Unstyled primitives | Unstyled primitives |
| Install model | Copy-paste into your repo | npm package | npm package |
| Backing | Community / Vercel interest | MUI team (full-time) | WorkOS (acquired) |
| TypeScript | ✅ | ✅ Excellent | ✅ Good |
| Accessibility | ✅ (via primitives) | ✅ | ✅ |
| Combobox / multi-select | ✅ (via cmdk) | ✅ Better API | ⚠️ Limited |
| Animation primitives | ✅ | ✅ | ✅ |
| CSS Variables theming | ✅ | ✅ | ✅ |
| Visual Builder | ✅ (Feb 2026) | ❌ | ❌ |
| Bundle size (dialog) | ~8KB | ~6KB | ~9KB |
| React version | 18/19 | 18/19 | 18 (19 in progress) |
What Each Actually Is
Understanding these three tools requires being clear about what layer they occupy:
Radix UI and Base UI are headless component primitives — they handle accessibility, keyboard navigation, ARIA attributes, and interaction logic with zero styling. You get behavior, not appearance. You add the CSS.
shadcn/ui is not a component library in the npm sense. It's a collection of pre-built components using Tailwind CSS, built on top of headless primitives (originally Radix, now Base UI too). When you run npx shadcn@latest add button, it copies the source code for a Button component into your project at components/ui/button.tsx. You then own that code — you can modify it however you want. There's no package to update.
This distinction matters for how you evaluate them:
# Radix/Base UI — installed as a dependency, updated via npm
npm install @radix-ui/react-dialog
npm install @base-ui-components/react
# shadcn/ui — components copied into your project
npx shadcn@latest add dialog
# Creates: components/ui/dialog.tsx
# You own this file. Edit it freely.
shadcn/ui: The Copy-Paste Revolution
The "install npm package vs copy into your project" distinction sounds like a minor implementation detail. In practice, it changes the entire maintenance model:
// After running: npx shadcn@latest add dialog
// This file is in YOUR repo at components/ui/dialog.tsx
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
You own this code. You see every className. You can change the animation, the overlay color, the z-index, the transition timing — all without fighting a library's theming system. Want a drawer instead of a centered modal? Edit the classes.
This is fundamentally different from installing MUI or Mantine, where customizing deeply means fighting overrides.
The February 2026 Visual Builder
shadcn's biggest 2026 addition is the Visual Builder — an interactive configurator that lets you adjust variants, sizes, and appearances visually, then copies the exact Tailwind code for your specific configuration:
# No install needed — open the builder at:
# https://ui.shadcn.com/builder
# It generates the exact component code for your choices:
# - Variant (default/destructive/outline/secondary/ghost/link)
# - Size (default/sm/lg/icon)
# - Custom colors/spacing
# Then copy the output to your components/ui/button.tsx
For teams that struggled with "what Tailwind classes produce the exact style I want," this removes the main friction point. You configure visually, get the code, own the result.
Switching shadcn/ui from Radix to Base UI
The February 2026 update that went somewhat under the radar: shadcn/ui now officially supports Base UI as the underlying primitive layer, in addition to Radix:
# Initialize shadcn with Base UI primitives (new in 2026)
npx shadcn@latest init --base-ui
# Or add individual components using Base UI
npx shadcn@latest add dialog --primitive=base-ui
The resulting components look identical to the user, but the underlying primitive has better TypeScript types and a more consistent API. For new projects, this is worth choosing. For existing Radix-based shadcn projects, migration isn't required.
Base UI: MUI's Headless Bet
Base UI is the MUI (Material UI) team's headless component library, extracted from their earlier "Unstyled components" offering and rebuilt from the ground up. The key difference from Radix:
Full-time engineering backing. Radix is maintained by the WorkOS team with a small core team. Base UI has dedicated MUI engineers working on it as a primary product investment. MUI serves millions of developers — they have strong incentives to keep Base UI production-quality.
// Base UI Dialog — cleaner API surface
import * as Dialog from "@base-ui-components/react/dialog";
function ConfirmDialog({ open, onOpenChange, onConfirm }) {
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Dialog.Portal>
<Dialog.Backdrop className="dialog-backdrop" />
<Dialog.Popup className="dialog-popup">
<Dialog.Title>Confirm Action</Dialog.Title>
<Dialog.Description>
This action cannot be undone.
</Dialog.Description>
<div className="dialog-actions">
<Dialog.Close>Cancel</Dialog.Close>
<button onClick={onConfirm}>Confirm</button>
</div>
</Dialog.Popup>
</Dialog.Portal>
</Dialog.Root>
);
}
The naming is cleaner: Dialog.Popup instead of DialogContent, Dialog.Backdrop instead of DialogOverlay. Minor difference in isolation, but consistent across all components.
Base UI's Combobox: Where It Genuinely Wins
Radix has been criticized for weak support for complex interaction patterns, particularly comboboxes and multi-select. The Radix Select primitive is intentionally limited, and building a proper searchable multi-select on top of it requires significant custom code:
// Radix approach to searchable select — requires cmdk workaround
import * as Select from "@radix-ui/react-select";
// Radix Select doesn't support search natively
// shadcn/ui uses cmdk (Command) for this pattern
// Base UI Combobox — built-in search support
import * as Combobox from "@base-ui-components/react/combobox";
function TagSelector({ tags, onSelect }) {
return (
<Combobox.Root multiple>
<Combobox.Input placeholder="Search tags..." />
<Combobox.Popup>
{tags.map(tag => (
<Combobox.Item key={tag.id} value={tag.id}>
<Combobox.ItemText>{tag.name}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Popup>
</Combobox.Root>
);
}
// Multi-select combobox with search, built natively
If your application needs complex form interactions — multi-select dropdowns, searchable selects, tag inputs — Base UI's primitive set handles them more naturally than Radix.
Radix UI: The Market Leader with a Question Mark
With 130M monthly downloads, Radix UI is the most widely used headless component library in the React ecosystem. It's the primitive layer under shadcn/ui (in its default configuration), Mantine UI, and dozens of other component libraries.
// Radix — the primitives you're probably already using
import * as Dialog from "@radix-ui/react-dialog";
import * as Select from "@radix-ui/react-select";
import * as Tooltip from "@radix-ui/react-tooltip";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
// Fully accessible, keyboard navigable, WAI-ARIA compliant
// Every property is documented, every pattern tested
The concern isn't whether Radix is good — it is good. The concern is trajectory. After WorkOS acquired Radix, the update cadence slowed. Several long-standing issues (combobox support, React 19 compatibility updates) moved slowly through the pipeline. The core team is smaller than Base UI's now.
For projects already built on Radix: don't migrate. The primitives work, the ecosystem is massive, and the API won't break overnight. For new projects choosing between Radix and Base UI as a primitive foundation: Base UI has momentum on its side.
React 19 Compatibility
// Radix — React 19 compatibility update was delayed
// Some @radix-ui/* packages needed updates for React 19's new ref handling
// Most updated by late 2025, but caused upgrade friction
// Base UI — built with React 19 in mind from the start
// No compatibility issues with the new ref transformation
Teams upgrading to React 19 encountered some rough edges with Radix packages that took months to fully resolve. Base UI was designed after React 19's changes were known and avoids these patterns.
Bundle Size Reality
Component: Dialog (open/close with animation)
Radix @radix-ui/react-dialog:
Package size: 9.2KB (gzipped)
Dependencies: @radix-ui/react-portal, @radix-ui/primitive, etc.
Total with deps: ~28KB
Base UI @base-ui-components/react (dialog only):
Package size: 6.4KB (gzipped)
Self-contained: yes (fewer cross-package deps)
Total: ~18KB
shadcn/ui Dialog (your compiled code + Radix):
Component code: ~3KB (Tailwind, compiled)
Runtime: Radix primitives (see above)
Total: ~31KB but includes styles
The numbers are close enough that bundle size shouldn't drive your decision. What matters more: Radix has many cross-package dependencies (each primitive is a separate package), which can inflate your node_modules significantly on larger projects. Base UI is more self-contained.
TypeScript Integration
// Radix — solid TypeScript, component-level types
import * as Dialog from "@radix-ui/react-dialog";
// The types are there but sometimes overly broad
type DialogContentProps = React.ComponentPropsWithoutRef<
typeof Dialog.Content
>; // Works but verbose
// Base UI — excellent TypeScript, consistent naming
import * as Dialog from "@base-ui-components/react/dialog";
// Sub-path imports give you tree-shaking + precise types
type DialogPopupProps = React.ComponentPropsWithoutRef<
typeof Dialog.Popup
>;
// Base UI components consistently expose:
// - render prop for custom element types
// - className string or function receiving state
// - All HTML attributes via ...props
function StyledDialog({ open }: { open: boolean }) {
return (
<Dialog.Popup
// className can be a function receiving component state
className={(state) =>
state.open ? "dialog-open" : "dialog-closed"
}
/>
);
}
Base UI's state-based className function is a pattern that makes conditional styling clean without needing data-[state=open]: Tailwind selectors.
Choosing Your Stack in 2026
Recommendation for new Next.js/React projects:
# 1. Install shadcn/ui with Base UI primitives (best of both worlds)
npx shadcn@latest init --base-ui
# 2. Add components as you need them
npx shadcn@latest add button dialog dropdown-menu tooltip
# 3. Customize the generated code in components/ui/
# — You own it. Tailwind classes are yours to change.
This gives you: shadcn's pre-built components and Visual Builder, Base UI's better primitives under the hood, and ownership of all the code.
For custom design systems (no shadcn):
# Base UI — unstyled primitives with modern API
npm install @base-ui-components/react
# Use CSS modules, CSS-in-JS, or Tailwind — your choice
# Better comboboxes, cleaner types, active development
For existing Radix-based projects:
# Don't migrate unless you have specific pain points.
# Radix works. 130M downloads/month says so.
# Wait for shadcn/ui's migration tooling if you ever want to switch.
The Three-Layer Mental Model
The cleanest mental model for understanding these three:
Layer 3: shadcn/ui
↓ Pre-built, Tailwind-styled, copy-pasted into your project
↓ Uses Layer 2 as primitives (configurable: Radix or Base UI)
Layer 2: Radix UI / Base UI
↓ Unstyled, accessible, behavior-only
↓ You provide all styling
↓ Both implement ARIA patterns correctly
Layer 1: Your application
↓ Uses whichever combination serves your needs
Most developers don't need to choose between all three. They choose shadcn/ui (which bundles the decision about Layer 2) or they choose a headless primitive directly (Radix or Base UI) for a custom design system. The 2026 update that shadcn/ui supports both Radix and Base UI as primitive layers means you're no longer locked in.
Compare shadcn/ui, Base UI, and Radix UI download trends at PkgPulse.
Related: React 19 vs React 18 2026 · Tailwind CSS vs CSS Modules 2026 · MUI vs Ant Design 2026
See the live comparison
View shadcn ui vs. radix ui on PkgPulse →