Skip to content
Intermediate· 10 min read

shadcn/ui and Base UI

shadcn/ui is a code-distribution platform that copies beautifully styled React components directly into your project, while Base UI is a headless library of accessible React primitives — and as of 2026, they work together.

Why This Matters

Every frontend team faces the same tension: how fast can you build without giving up control?

Traditional component libraries (Material UI, Ant Design, Chakra) give you speed — import a ready-made <Button> and you're done. But when you need to deviate from the library's design, you're fighting against layers of overrides, wrapping components, and fighting CSS specificity. You get velocity upfront, then pay for it in customization pain later.

shadcn/ui and Base UI invert this trade-off. Together (or separately) they represent a philosophical shift: you own your UI code from day one, and styling is something you control — not something you fight against.

Understanding these tools is essential for any React developer building modern web applications in 2026, whether you're prototyping a quick dashboard or engineering a full production design system.

Prerequisites

Prerequisites

This article assumes you're comfortable with React (components, props, JSX), Tailwind CSS (utility-first CSS and the className convention), and basic familiarity with npm/yarn package management. If any of those feel shaky, the examples below are written to be readable regardless — you'll still get the big picture.

Core Idea

Think of frontend UI code as having three layers:

LayerWhat it doesExample
Functionality & AccessibilityKeyboard navigation, focus management, ARIA attributesHandles arrow keys in a dropdown, announces state to screen readers
StylingColors, spacing, typography, animationsMakes the button blue with rounded corners
DistributionHow components get into your projectnpm install vs. copy-paste

Base UI handles Layer 1 (functionality + accessibility) — it gives you components that work correctly but have zero visual design.

shadcn/ui is a distribution system that wraps Layer 1 (via Radix or Base UI) and adds Layer 2 (beautiful Tailwind styling), then delivers the whole thing via Layer 3 as copy-pasted source code rather than an npm dependency.

The revolutionary insight of shadcn/ui is: instead of installing a package you don't control, you copy the source code into your project. You own it. You edit it. You never need to wait for a library update to change how a button looks.

How It Actually Works

Base UI — The Engine Under the Hood

Base UI (package: @base-ui/react) is a headless component library maintained by the MUI team — the same team behind Material UI. "Headless" means the components handle all the hard stuff (keyboard navigation, screen-reader announcements, focus traps, state management) but render zero CSS. You bring all the styles.

It reached v1.0 in February 2026 after two years of development, shipping 35 accessible components. The team behind it includes engineers who previously worked on Radix UI and Floating UI, making Base UI effectively the next evolution of the headless-component concept.

How it looks in code:

import { Button } from '@base-ui/react';

// This renders a fully accessible <button> with keyboard handling
// and ARIA attributes — but no visual style at all
function MyComponent() {
  return <Button>Click me</Button>;
  // Renders: <button tabindex="0" data-baseui-button="">Click me</button>
  // No colors, no padding, no border — just a naked button element
}

You style it however you like:

// With Tailwind
<Button className="bg-blue-500 text-white px-4 py-2 rounded-md">
  Click me
</Button>

// With plain CSS modules
<Button className={styles.primaryButton}>
  Click me
</Button>

What Base UI gives you out of the box:

  • Keyboard navigation — arrow keys in menus, Escape to close dialogs, Tab through form controls
  • Focus management — focus is trapped inside modals, returned to the trigger on close
  • ARIA attributes — role, aria-expanded, aria-selected, aria-labelledby, etc., all set automatically
  • Unique features like "detached triggers" — one popup element shared across multiple triggers (not available in any other headless library)

shadcn/ui — The Distributor and Styler

shadcn/ui (created by @shadcn, now at 117k+ GitHub stars) is not a library you npm install. It's a code distribution platform built around a CLI that generates component source files directly into your project.

The key philosophy (from the official docs):

"This is not a component library. It is how you build your component library."ui.shadcn.com/docs

How it works step by step:

  1. Initialize — run npx shadcn@latest init to configure your project (Tailwind colors, CSS variables, component folder)
  2. Add components — run npx shadcn@latest add button dialog dropdown-menu
  3. The CLI downloads the source code for each component and places it in components/ui/button.tsx
  4. You import and use like any local component:
import { Button } from "@/components/ui/button"

function MyPage() {
  return <Button>Click me</Button>
  // Renders a fully styled, accessible button with Tailwind classes
}

What makes this different from npm?

Aspectnpm Package (MUI, Chakra)shadcn/ui
Code locationnode_modules/ (read-only)components/ui/ (yours to edit)
Updatingnpm update (may break)Manual diff or re-add
CustomizationOverride via theme/sx propsDirectly edit the .tsx file
VersioningLibrary version pinsNo version — you have the code
Bundle sizeTree-shake or whole libraryOnly components you add

The five principles (from the official docs):

  1. Open Code — you own the actual component source. Edit a button's padding by changing the JSX, not by overriding CSS.
  2. Composition — all components share a common, composable API. A <Dialog>, <Popover>, and <DropdownMenu> all follow the same patterns.
  3. Distribution — a flat-file registry schema and CLI make it trivial to distribute your own components the same way.
  4. Beautiful Defaults — carefully chosen Tailwind styles mean components look polished immediately. Two style presets: default (larger, more rounded) and new-york (tighter, shadow-heavy).
  5. AI-Ready — open code and consistent APIs make it straightforward for LLMs to read, understand, and generate new components that match your design system.

Over 60 components are available as of mid-2026, including: Button, Card, Dialog, Dropdown Menu, Navigation Menu, Sidebar, Data Table, Chart, Form controls (Input, Select, Checkbox, Radio Group, Switch), Toast, Tabs, Accordion, Carousel, and more.

How They Work Together

In early 2026, shadcn/ui added support for Base UI as the underlying primitive layer alongside Radix UI. You choose which one to use:

# Use Radix UI (original default)
npx shadcn@latest init

# Use Base UI instead
npx shadcn@latest init --base Base UI

When you choose Base UI as the primitive, shadcn/ui's code generation produces components that import from @base-ui/react instead of @radix-ui/*. You still get shadcn's beautiful Tailwind styling on top — but the accessibility, keyboard handling, and component state management come from Base UI.

This combination gives you:

  • Base UI's professionally maintained, well-tested accessibility layer
  • shadcn/ui's beautiful preset styling and open-code philosophy
  • Full editability — you own every line

Why this matters

Radix UI (the original shadcn primitive) faced uncertainty after its company was acquired. Base UI, backed by MUI's dedicated engineering team, represents a more stable long-term foundation — and shadcn/ui lets you switch to it with a single CLI flag.

The Ecosystem in Context

graph TD
    A["🚀 Your React / Next.js Project"] --> B["🎨 shadcn/ui (styled components)"]
    B --> C{"⚡ Choose primitive layer"}
    C --> D["🔧 Base UI (@base-ui/react)"]
    C --> E["🧩 Radix UI (@radix-ui/*)"]
    D --> F["👥 MUI team (dedicated engineers)"]
    E --> G["🧩 Radix team (acquired)"]
    H["🌈 Tailwind CSS"] --> B

    classDef project fill:#d4a853,stroke:#c49a3c,stroke-width:2px,color:#050505
    classDef shadcn fill:#a78bfa,stroke:#8b5cf6,stroke-width:2px,color:#ffffff
    classDef decision fill:#d4a853,stroke:#c49a3c,stroke-width:2px,color:#050505
    classDef baseui fill:#1a3a2a,stroke:#34d399,stroke-width:2px,color:#34d399
    classDef radix fill:#2d2d2d,stroke:#6b7280,stroke-width:2px,color:#c9c9c9
    classDef team fill:#2d2d2d,stroke:#4a4a4a,stroke-width:2px,color:#a3a3a3
    classDef tailwind fill:#1c1c3a,stroke:#5b8cf6,stroke-width:2px,color:#87CEEB

    class A project
    class B shadcn
    class C decision
    class D baseui
    class E radix
    class F,G team
    class H tailwind

Worked Example: Building a Login Form

Let's walk through building a login form with shadcn/ui using Base UI — the combination represents "2026 best practice."

Step 1: Set up the project

npx create-next-app@latest my-app --typescript --tailwind --eslint
cd my-app
npx shadcn@latest init --base Base UI

The CLI configures CSS variables, sets up Tailwind, and detects your framework.

Step 2: Add the components you need

npx shadcn@latest add form input button card label

This generates the following files in components/ui/:

  • button.tsx — styled <Button> using Base UI's @base-ui/react/button
  • input.tsx — styled <Input> using Base UI primitives
  • card.tsx — simple layout container
  • form.tsx — form wrapper using react-hook-form
  • label.tsx — accessible label component

Step 3: Build the form

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Label } from "@/components/ui/label"

export function LoginForm() {
  return (
    <Card className="w-96 mx-auto mt-20">
      <CardHeader>
        <CardTitle>Sign In</CardTitle>
      </CardHeader>
      <CardContent>
        <form className="space-y-4">
          <div>
            <Label htmlFor="email">Email</Label>
            <Input id="email" type="email" placeholder="you@example.com" />
          </div>
          <div>
            <Label htmlFor="password">Password</Label>
            <Input id="password" type="password" />
          </div>
          <Button type="submit" className="w-full">
            Sign In
          </Button>
        </form>
      </CardContent>
    </Card>
  )
}

Step 4: Customize

Because every component is local source code, you can open components/ui/button.tsx and change:

// Original
className={cn(
  "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors",
  "h-10 px-4 py-2",
  // ...
)}

// Customized — larger, more rounded, with custom hover
className={cn(
  "inline-flex items-center justify-center whitespace-nowrap rounded-xl text-base font-semibold transition-all",
  "h-12 px-6 py-3",
  "bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700",
  // ...
)}

No override system. No sx prop. No createTheme. Just edit the file.

What you get for free (from Base UI)

  • Keyboard accessible — Tab through fields, Enter to submit, Escape to clear
  • Screen reader friendly — proper labels, error announcements, aria-required attributes
  • Focus management — visible focus rings on all interactive elements

Common Misconceptions

"shadcn/ui is just another component library like MUI"

This is the most common misunderstanding. MUI is an npm package you install as a dependency — you don't own the source code. shadcn/ui gives you the actual source files. When MUI releases v6, your upgrade path is through their migration guide. When you edit a shadcn component, you just edit the file. There is no "version" to upgrade.

"Base UI is just Material UI without the Material Design"

Not quite. Base UI is a completely separate codebase from Material UI, built from the ground up as headless primitives. It shares the same maintainers (the MUI team) but is a different product with a different philosophy — zero opinions about styling, maximum flexibility.

"I have to choose between Radix and Base UI for my shadcn project"

You can switch. The CLI's --base flag lets you pick your primitives at init time, and future versions make it possible to migrate components from one primitive to another. It's not a permanent lock-in.

"Copying code means I lose the ability to update"

shadcn/ui handles this through its CLI and diff system. Run npx shadcn@latest add button --diff to see what changed in the upstream registry compared to your local copy. You can merge updates selectively, just like you do with git.

"Headless UI libraries are just for people who want to write lots of CSS"

The real value of headless libraries (Base UI, Radix) is not about CSS — it's about accessibility and interaction correctness. Proper keyboard navigation, focus management, and ARIA attributes are extremely tedious to implement from scratch and easy to get subtly wrong. Headless libraries do it for you so you can focus on what makes your UI unique.

Key Takeaways

  • shadcn/ui is a code-distribution platform, not a package. It copies component source code into your project. You own it, you edit it, you never fight a library's opinionated theming system.
  • Base UI is a headless (unstyled) React library with 35 professionally built, accessible components. It handles keyboard nav, focus management, and ARIA — and renders zero CSS. You bring the styles.
  • As of 2026, shadcn/ui supports Base UI as a primitive layer alongside Radix UI. The combination gives you beautiful default styling (shadcn) + professionally maintained accessibility (Base UI) + full code ownership (shadcn's copy-paste model).
  • Base UI is backed by the MUI team with dedicated engineers — offering a more stable long-term commitment than Radix UI.
  • When to use shadcn/ui: Fast, polished UI development — dashboards, SaaS apps, landing pages, any React/Next.js project where you want beautiful defaults with full control.
  • When to use Base UI alone: Building a fully custom design system from scratch, where shadcn's preset styles would be more to undo than leverage.
  • When to use both (shadcn + Base UI): You want shadcn's convenience and beautiful defaults, but you want Base UI's modern, well-maintained primitives as the foundation. This is the recommended path for new projects in 2026.

Open Questions

Where the evidence is thin

  • Long-term ecosystem stability: Base UI v1.0 is very recent (February 2026). While MUI's backing is strong, the library's long-term adoption trajectory and community robustness are still being established.
  • Migration tooling: The ability to seamlessly switch a shadcn project from Radix to Base UI primitives — or migrate components one at a time — is relatively new. Real-world migration stories from production projects are still sparse as of mid-2026.
  • Performance at scale: shadcn/ui's model of generating individual files works well for small-to-medium projects. How this scales in very large monorepos with hundreds of components is an area where the community is still gaining experience.

References