shadcn/ui
Status: 🟩 COMPLETE (🟦 LIVING) Last updated: 2026-06-20 Plain-English tagline: A library of beautiful React components you copy into your project (rather than install). Built on Tailwind + Radix. The default UI kit for new Next.js projects in 2026.
In plain English
Traditional component libraries (Material UI, Chakra, Mantine) are installed as npm packages: npm install @mui/material, then import components from the package. Your project depends on the library; theming and customization happen through the library’s APIs.
shadcn/ui does something different: you run a CLI to copy the component’s source code into your project. There’s no @shadcn/ui package in your node_modules. The component lives at components/ui/button.tsx, and it’s yours to edit directly.
Why this matters:
- Total customization — change anything, no fighting the library’s API
- No version lock-in — the code is in your repo
- No bundle bloat — only the components you actually use
- Updates happen on YOUR schedule — pull new versions via the CLI when ready
shadcn/ui components are built on:
- Tailwind CSS for styling
- Radix UI for behavior (keyboard navigation, focus management, accessibility)
- TypeScript throughout
- CVA (class-variance-authority) for variant management
The result: accessible, beautiful, customizable components. Created by Shadcn (a developer pseudonym) in 2023. Took over rapidly. Now the default for new React/Next.js projects.
Why it matters
For non-coders building real webapps with Claude:
- Looks professional. Components have great defaults out of the box. No “this looks like a 2010 admin panel” feeling.
- Accessible. Built on Radix UI, which handles keyboard nav, ARIA, focus rings automatically.
- Tailwind throughout. Customization is just editing Tailwind classes — no learning a separate theming API.
- Plays well with Claude. Claude knows shadcn/ui patterns well. “Add a dialog using shadcn” produces clean code.
- Composable. Components are designed to be composed and customized, not used as black boxes.
For your stack (Next.js + Tailwind), shadcn/ui is essentially the natural extension.
How it works
1. Initial setup
npx shadcn@latest initPrompts:
- Style (Default or New York) — both fine; pick one and stick with it
- Base color (slate, gray, zinc, neutral, stone)
- CSS variables (yes — enables theming)
The CLI creates:
components/ui/— where copied components will landlib/utils.ts— helper functions (cn()for merging Tailwind classes)components.json— shadcn config- Updates
tailwind.config.tsto add shadcn’s theme tokens
2. Add components on demand
npx shadcn@latest add button
npx shadcn@latest add card
npx shadcn@latest add dialog
npx shadcn@latest add dropdown-menuEach command copies that component’s source code into components/ui/. You can see, edit, and customize the actual files.
3. Use them
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export function MyPage() {
return (
<Card>
<CardHeader>
<CardTitle>Hello</CardTitle>
</CardHeader>
<CardContent>
<Button>Click me</Button>
<Button variant="outline">Outlined</Button>
<Button variant="destructive" size="sm">Delete</Button>
</CardContent>
</Card>
);
}Variants (primary, secondary, outline, destructive, ghost) and sizes (sm, default, lg, icon) are built in. Customize by editing components/ui/button.tsx directly.
The major components available
A non-exhaustive list of what you can add:
Forms: Button, Input, Textarea, Label, Checkbox, Radio Group, Select, Switch, Slider, Toggle, Form (RHF + Zod integration)
Layout: Card, Separator, Tabs, Accordion, Collapsible, Aspect Ratio, Scroll Area, Resizable
Overlays: Dialog, Alert Dialog, Sheet, Drawer, Popover, Hover Card, Tooltip, Context Menu, Dropdown Menu, Menubar, Command (cmd-k)
Feedback: Alert, Toast (Sonner integration), Skeleton, Progress, Spinner, Badge
Data display: Table, Data Table (TanStack Table integration), Avatar, Calendar, Date Picker
Navigation: Navigation Menu, Breadcrumb, Pagination
Other: Theme provider, Toggle group, Carousel, Chart (Recharts integration), Sidebar, Resizable panels
Roughly 50+ components, with new ones added regularly.
A concrete example: a dialog with a form
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function CreatePostDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button>New post</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create a new post</DialogTitle>
</DialogHeader>
<form className="space-y-4">
<div>
<Label htmlFor="title">Title</Label>
<Input id="title" name="title" />
</div>
<div>
<Label htmlFor="body">Body</Label>
<Input id="body" name="body" />
</div>
<Button type="submit">Publish</Button>
</form>
</DialogContent>
</Dialog>
);
}Out of the box:
- The dialog traps focus when open
- Escape closes it
- Clicking outside closes it
- The trigger gets
aria-expanded/aria-controls - Smooth open/close animations
- Mobile-responsive
You write 20 lines of JSX; you get a production-quality modal. The accessibility is from Radix UI under the hood.
Customizing components
Since the source is in your repo, you can edit anything. To change the button’s default variant:
// components/ui/button.tsx
const buttonVariants = cva(
"inline-flex items-center justify-center ...", // base
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
// ← change this color/treatment freely
},
// ...
},
}
);Edit, save, the change applies everywhere the button is used.
For theme-wide changes (colors, radius, fonts), modify CSS variables in app/globals.css:
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%; /* ← change brand color */
--radius: 0.5rem; /* ← change all border-radius */
}
}Components reference these tokens (bg-primary, rounded-md), so theme changes ripple everywhere.
Themes
shadcn/ui has a theme picker on its website: change brand color + style + radius, copy the resulting CSS variables, paste into your globals.css. Instant rebrand.
For dark mode, the dark variant uses different CSS variable values, switched by the .dark class on <html>. Pairs naturally with next-themes (see How-to: Set up dark mode).
The mental model
shadcn/ui isn’t a library you install — it’s a distribution system for component recipes. Think of it as “really high-quality component templates, given to you in your own code.”
Implications:
- Updates are opt-in. If shadcn improves a component, you can pull the new version with the CLI (and merge any local changes manually).
- There’s no upgrade pain. You’re never blocked by “the library doesn’t support X yet.”
- You own the bug surface too. If a component has a bug, you fix it in your code.
Common gotchas
-
asChildconfuses people. When a shadcn component takesasChild, it doesn’t render its own element — it adopts its child element’s role.<DialogTrigger asChild><Button>...</Button></DialogTrigger>makes the Button itself the trigger, instead of wrapping it in another element. Read the Radix docs when confused. -
Updating components.
npx shadcn@latest add buttonoverwrites your customizations if you say yes. The CLI usually warns and asks. Diff before overwriting. -
Customization scattered. Once you’ve customized many components, updates become harder. Discipline: keep customizations focused; document them.
-
Tailwind class merging. If you pass
classNameto a shadcn component, thecn()utility merges with defaults — last wins. If you don’t see your override, check class specificity / order. -
next/linkand<a>tags inside. Some shadcn components render their own anchors. UseasChildto compose with<Link>instead of double-anchoring. -
Theme conflicts. If you change
--primaryetc. but the components don’t update, you may have multiple CSS variable definitions clashing. Globals.css is the canonical home. -
Forms with shadcn’s Form component require react-hook-form + Zod. It’s well-designed but the learning curve is real. For simple forms, plain inputs are fine.
-
Charting (Recharts wrapper). The shadcn chart wrapper is a thin layer over Recharts. For complex charts, you may need to drop down to Recharts directly.
-
Mixing with other component libraries. Possible but messy. shadcn + your own custom components is the cleanest path. Mixing shadcn + MUI is asking for trouble.
-
Skipping the initial
init. Components reference utilities (cn(), theme variables) that the init creates. Don’tadda component without init. -
Server Components vs Client Components. Many shadcn components use state, so they’re Client Components. Pages that consume them can still be Server Components; the boundary happens at the component import.
See also
- Tailwind CSS 🟩 — what shadcn is built on
- React 🟩
- TypeScript 🟩
- Next.js 🟩 🟦
- Accessibility (a11y) 🟩 — Radix handles much of this
- Dark mode 🟩
- How-to: Set up dark mode 🟩
- Glossary: ui
Sources
- shadcn/ui docs
- Radix UI docs — what shadcn wraps
- Tailwind CSS docs
- class-variance-authority — the variant system shadcn uses