Understanding shadcn/ui
Not a library. A copy-paste project.
Why shadcn/ui isn't on npm, what it actually gives you, and how that changes the maintenance trade-off.
Components live in your repo, not theirs.
shadcn/ui's central idea: instead of installing a UI library you depend on, copy the components into your repo. Run the CLI; it adds files like components/ui/button.tsx directly to your project. You own them outright — edit them freely, version them with the rest of your code, never fight a library upgrade. The trade: no automatic updates. If shadcn improves the Button component, your copy doesn't change unless you re-run the CLI.
Radix UI underneath.
shadcn components are styled wrappers around Radix UI primitives — the accessibility-first headless library from the WorkOS team. Radix handles keyboard navigation, ARIA attributes, focus management, click-outside detection, screen-reader announcements. shadcn adds Tailwind-styled chrome. The pairing is the entire value proposition: accessible by construction, beautiful by default, owned by you.
A worked component.
A typical shadcn Dialog is ~80 lines: import Radix primitives, wrap them with forwardRef components that apply Tailwind classes, export a friendly API. The generated file uses class-variance-authority for variant handling, clsx + tailwind-merge for class composition, and lucide-react for icons. Every shadcn install adds these as dependencies; one set of patterns, every component reuses.
shadcn add button
CLI writes components/ui/button.tsx
One command per primitive; one file per component.
npx shadcn@latest add button → button.tsx in your repo
= Owned, editable component
Theming via CSS variables.
shadcn's colour system uses CSS variables driven from the Tailwind config — --primary, --secondary, --accent, --destructive, plus their -foreground counterparts. Light/dark mode swaps the variables; components stay identical. To rebrand, edit the variables in globals.css; every component picks it up. That's the design-system surface area in one place.
When ownership becomes a burden.
The flip side of "you own the components": you maintain them. A breaking change in Radix UI requires editing every shadcn component you've installed. For a small project, this is fine — you only edit when needed. For a 500- component design system, the maintenance scales. Teams hit this wall around 200+ shadcn installs and start wondering whether a real package would have been better. The answer is project-dependent.
The right alternative is sometimes a real library.
shadcn fits projects that want full control of their components and have designers actively pushing visual changes through to code. Projects that want batteries-included UI without thinking about it are better served by Mantine, Chakra, or the original headless+styled-components route (Radix + your own styles). The shadcn model isn't universally right; it's right when ownership is a feature, not just a side effect.