shadcn UI: Complete Guide to the Most Popular React Component Collection (2026)
DesignRevision Editorial
· SaaS, frontend & developer tooling
shadcn UI changed how developers think about component libraries. Instead of installing a package and fighting with overrides, you copy the source code into your project and own every line. No runtime dependencies. No version conflicts. No specificity battles.
With 65,000+ GitHub stars and adoption by teams at Vercel, Supabase, and thousands of production applications, shadcn UI has become the default choice for React projects using Tailwind CSS. This shadcn ui guide covers everything from installation to production patterns.
Key Takeaways
If you remember nothing else:
- shadcn UI is not a library you install. It is a collection of components you copy into your project and own entirely
- Components are built on Radix UI primitives (accessible, headless) styled with Tailwind CSS utility classes
- The CLI (
npx shadcn-ui@latest) handles initialization and adding components. No npm package to manage- Theming uses CSS variables in your global stylesheet. Switch themes by changing variable values
- 50+ components cover forms, data tables, navigation, dialogs, and more with full WAI-ARIA accessibility
- Dark mode works out of the box through CSS variable toggling with next-themes or any class-based theme switcher
Table of Contents
- What Is shadcn UI?
- How It Differs from Traditional Libraries
- Installation and Setup
- Core Components
- Theming with CSS Variables
- Dark Mode
- Forms with React Hook Form and Zod
- Data Tables with TanStack Table
- Customizing Components
- The Registry and Custom Registries
- Production Best Practices
- Conclusion
What Is shadcn UI?
shadcn UI is a collection of beautifully designed, accessible React components that you add to your project through a CLI tool. Created by shadcn (Shadid Haque), it sits on top of two foundational technologies:
- Radix UI provides the headless, accessible primitives. These handle keyboard navigation, focus management, ARIA attributes, and interaction patterns
- Tailwind CSS provides the styling layer. Every visual aspect is controlled through utility classes and CSS variables
When you run npx shadcn-ui@latest add button, the CLI copies a button.tsx file into your components/ui directory. That file is yours. Edit it, extend it, or rewrite it entirely. There is no hidden dependency to update, no breaking changes from upstream releases, and no bundle size overhead from unused components.
This model resonated with developers so strongly that shadcn UI grew to 65,000+ GitHub stars, making it one of the most popular React UI projects. The Vercel dashboard, numerous SaaS starter kits, and thousands of production applications are built with shadcn components.
How It Differs from Traditional Libraries
The distinction matters because it changes how you build and maintain your UI.
| Aspect | shadcn UI | Traditional Libraries (MUI, Chakra) |
|---|---|---|
| Distribution | Source code copied via CLI | npm package installed |
| Styling | Tailwind utility classes + CSS variables | CSS-in-JS or pre-built CSS |
| Customization | Edit source files directly | Theme overrides and props |
| Bundle Impact | Only what you copy | Full library weight |
| Updates | Manual (your code) | npm update (may break) |
| Dependencies | Tailwind + Radix only | Heavy runtime libraries |
| Lock-in | None | Vendor-specific APIs |
Traditional libraries give you convenience at the cost of control. shadcn UI gives you control at the cost of some convenience. For most teams in 2026, the trade-off favors ownership, which is why shadcn UI adoption continues to accelerate.
Installation and Setup
Next.js (App Router)
The most common setup. If you are deciding between frameworks, our Vite vs Next.js comparison covers the trade-offs.
npx create-next-app@latest my-app --typescript --tailwind --eslint --app
cd my-app
npx shadcn-ui@latest init
The init command asks you to configure:
- TypeScript (recommended: yes)
- Style: Default or New York
- Base color: Slate, Gray, Zinc, Neutral, or Stone
- CSS variables for theming (recommended: yes)
components.jsonpath configuration
This generates a components.json config file, updates your globals.css with CSS variables, and creates a lib/utils.ts file with the cn() helper function.
Vite
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npx shadcn-ui@latest init
Configure path aliases in tsconfig.json and vite.config.ts for clean imports. The CLI detects Vite and adjusts configuration accordingly.
Adding Your First Component
npx shadcn-ui@latest add button
This copies button.tsx into your components directory. Use it like any React component:
import { Button } from '@/components/ui/button'
export function MyPage() {
return <Button variant="outline">Click me</Button>
}
Add multiple shadcn components at once:
npx shadcn-ui@latest add button card input dialog
Core Components
shadcn UI provides 50+ components organized by category. Here are the most commonly used:
| Category | Components | Use Cases |
|---|---|---|
| Actions | Button, Toggle, ToggleGroup | CTAs, settings, toolbars |
| Layout | Card, Sheet, Dialog, Drawer | Content containers, modals, side panels |
| Navigation | NavigationMenu, Tabs, Breadcrumb, Command | App navigation, tab interfaces, command palettes |
| Data Display | Table, Badge, Avatar, Skeleton | Lists, status indicators, loading states |
| Forms | Input, Select, Checkbox, Switch, Textarea, Form | User input, settings, registration |
| Feedback | Toast (Sonner), Alert, Progress, Tooltip | Notifications, status messages, loading indicators |
| Advanced | DataTable, Calendar, DatePicker, Combobox | Complex data management, scheduling |
Every component includes TypeScript types, accessibility attributes, and responsive behavior by default. The Next.js templates that ship with shadcn components give you a head start on common layouts.
Theming with CSS Variables
The theming system uses CSS variables defined in your globals.css. This approach is lighter than CSS-in-JS and works with server-side rendering without flash-of-unstyled-content issues.
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--muted: 210 40% 96.1%;
--accent: 210 40% 96.1%;
--destructive: 0 84.2% 60.2%;
--border: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
}
Components reference these variables through Tailwind classes like bg-primary, text-foreground, and border-border. To create a custom brand theme, change the variable values. Every component updates instantly because they all reference the same variables.
Creating multiple themes is as simple as defining additional CSS classes:
.theme-ocean {
--primary: 199 89% 48%;
--primary-foreground: 0 0% 100%;
}
.theme-forest {
--primary: 142 76% 36%;
--primary-foreground: 0 0% 100%;
}
Apply the class to your root element and every shadcn component adopts the new palette.
Dark Mode
Dark mode is built into the CSS variable system. Define a .dark class with inverted variable values:
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
/* remaining variables */
}
Implementation with next-themes
npx shadcn-ui@latest add sonner
npm install next-themes
Create a theme provider:
// components/theme-provider.tsx
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({ children, ...props }) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
Wrap your app layout:
// app/layout.tsx
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
Toggle with a button:
"use client"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
return (
<Button
variant="ghost"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
>
Toggle theme
</Button>
)
}
The system preference option (enableSystem) respects prefers-color-scheme automatically.
Forms with React Hook Form and Zod
shadcn UI includes a Form component that integrates React Hook Form with Zod validation. This is the recommended pattern for building forms in this shadcn ui tutorial.
npx shadcn-ui@latest add form input button
"use client"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import {
Form, FormControl, FormField,
FormItem, FormLabel, FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
const schema = z.object({
email: z.string().email("Enter a valid email"),
name: z.string().min(2, "Name must be at least 2 characters"),
})
export function SignupForm() {
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: { email: "", name: "" },
})
function onSubmit(values: z.infer<typeof schema>) {
// Handle form submission
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Your name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="[email protected]" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Sign up</Button>
</form>
</Form>
)
}
The Form components handle validation messages, error states, and accessibility labels automatically. Zod schemas provide type-safe validation that runs both client-side and server-side.
Data Tables with TanStack Table
The DataTable component combines shadcn UI's Table with TanStack Table (formerly React Table) for sorting, filtering, pagination, and row selection.
npx shadcn-ui@latest add table
npm install @tanstack/react-table
Define columns:
// columns.tsx
import { ColumnDef } from "@tanstack/react-table"
export const columns: ColumnDef<User>[] = [
{ accessorKey: "name", header: "Name" },
{ accessorKey: "email", header: "Email" },
{ accessorKey: "role", header: "Role" },
]
Create the data table:
// data-table.tsx
"use client"
import {
flexRender, getCoreRowModel,
useReactTable,
} from "@tanstack/react-table"
import {
Table, TableBody, TableCell,
TableHead, TableHeader, TableRow,
} from "@/components/ui/table"
export function DataTable({ columns, data }) {
const table = useReactTable({
data, columns, getCoreRowModel: getCoreRowModel(),
})
return (
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
)
}
Add sorting, filtering, and pagination by importing additional TanStack Table features. The shadcn Table component handles the styling while TanStack handles the data logic. This separation makes it straightforward to add server-side data fetching for large datasets.
Customizing Components
Since you own the source code, customization is direct file editing.
Adding Variants
Components use class-variance-authority (cva) for variant management:
// components/ui/button.tsx
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
outline: "border border-input bg-background hover:bg-accent",
ghost: "hover:bg-accent hover:text-accent-foreground",
// Add your custom variant:
brand: "bg-blue-600 text-white hover:bg-blue-700",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
// Add your custom size:
xl: "h-14 rounded-lg px-10 text-base",
},
},
}
)
The cn() Utility
The cn() function merges Tailwind classes intelligently using clsx and tailwind-merge:
import { cn } from "@/lib/utils"
<Button className={cn("w-full", isActive && "bg-green-500")} />
This prevents class conflicts where bg-primary and bg-green-500 would both be applied. tailwind-merge resolves the conflict by keeping the last relevant class.
The Registry and Custom Registries
The shadcn UI registry at ui.shadcn.com hosts all official components. The CLI pulls from this registry when you run add commands.
Custom registries let teams host their own component collections. Point components.json to a custom URL:
{
"$schema": "https://ui.shadcn.com/schema.json",
"registries": {
"my-team": {
"url": "https://components.mycompany.com/registry"
}
}
}
Then add components from your private registry:
npx shadcn-ui@latest add my-team/data-card
This pattern works well for design systems where a core team maintains branded components and product teams consume them. It scales shadcn components across large organizations without losing the ownership model.
Production Best Practices
File Organization
Keep shadcn components in /components/ui/ untouched when possible. Create composed components in /components/ that combine UI primitives:
components/
ui/ # shadcn primitives (button, card, input)
dashboard/ # composed components (metric-card, data-grid)
forms/ # form compositions (signup-form, settings-form)
Performance
- Lazy load heavy components. Dialog, Sheet, and Calendar add weight. Use
next/dynamicfor components that render below the fold or on interaction - Tree-shaking is automatic. Since you only copy the components you use, unused shadcn components are never in your bundle
- CSS variables render server-side. No flash of unstyled content compared to CSS-in-JS libraries
Keeping Components Updated
shadcn UI does not auto-update since you own the code. To pull improvements:
- Check the shadcn UI changelog for relevant updates
- Compare your component with the latest registry version
- Merge changes manually or re-add the component and port your customizations
Most teams find that after initial customization, they rarely need upstream updates. The Radix primitives handle accessibility updates independently through npm.
Integration with ORMs and Backends
When building full-stack applications, shadcn data tables pair naturally with server components and database queries. If you are choosing an ORM, our Prisma vs Drizzle comparison covers how each handles typed queries that feed directly into TanStack Table column definitions. For database hosting to back your shadcn-powered app, the Supabase vs Neon comparison breaks down the options.
Ship apps faster with AI
Generate production-ready Next.js apps from a prompt. Full code ownership, deploy anywhere, stunning design output.
Conclusion
shadcn UI represents a fundamental shift in how developers consume UI components. Instead of depending on a library that controls your styling, you get source code that you control.
The trade-off is clear: you sacrifice automatic updates for complete ownership. You sacrifice one-line npm installs for a CLI that copies files. You sacrifice opinionated design systems for a foundation you shape yourself.
For most React projects in 2026, that trade-off is worth it. The combination of Radix accessibility, Tailwind styling, and full code ownership gives you polished interfaces without the technical debt that traditional component libraries accumulate over time.
Start with npx shadcn-ui@latest init, add the components you need, and make them yours.
Related Resources
Frequently Asked Questions
-
Not in the traditional sense. shadcn UI is a collection of reusable components that you copy directly into your project via a CLI tool. Unlike libraries like MUI or Chakra that install as npm dependencies, shadcn UI gives you the actual source code. You own every file, can modify anything, and have zero runtime dependency on the project. This approach eliminates version conflicts, reduces bundle size, and gives you complete control over styling and behavior.
-
Yes. shadcn UI officially supports Next.js, Vite, Remix, Astro, and Laravel. The components are built on React with Radix UI primitives and Tailwind CSS, so any React-based framework that supports Tailwind works. Vue and Svelte ports also exist through community adapters. The CLI detects your framework and configures the project accordingly during initialization.
-
Tailwind CSS is a core dependency. The components use Tailwind utility classes for all styling, and the theming system relies on CSS variables that integrate with the Tailwind configuration. While you could theoretically strip out Tailwind classes and replace them with custom CSS, this defeats the purpose of using shadcn UI. If you prefer a different styling approach, consider Radix Themes or Headless UI as alternatives.
-
Edit the source files directly. When you add a component, the CLI copies its TypeScript file into your components directory. Change Tailwind classes, modify the JSX structure, add new props, or adjust animations. The class-variance-authority library handles component variants. Use the cn utility function to merge conditional classes. Since you own the code, there are no override limitations or specificity battles.
-
Yes. Components are built on Radix UI primitives which implement WAI-ARIA specifications. This includes proper ARIA roles, keyboard navigation, focus management, and screen reader support. Dialog, Dropdown, and AlertDialog components handle focus trapping and escape key behavior correctly. The accessibility is baked into the headless layer so it persists regardless of how you customize the visual styling.
-
The fundamental difference is distribution. MUI and Chakra install as npm packages with pre-compiled styles. shadcn UI copies source code into your project. MUI is better for teams that want Material Design consistency out of the box. Chakra is better for teams that prefer a prop-based styling API. shadcn UI is better for teams that want full code ownership, Tailwind-native styling, and zero runtime overhead from a component library. Most developers in 2026 choose shadcn UI for new projects because of its flexibility and smaller footprint.
Next.js SaaS Starter Kit
Pre-built auth, billing, and dashboard. Launch your SaaS in days, not weeks.
Join 50k+ subscribers
Web dev, SaaS, growth & marketing. Weekly.
Keep Learning
More articles you might find interesting.