shadcn/ui: The Complete Component Library Guide for 2026
Complete guide to shadcn/ui covering installation across Next.js, Vite, Remix, and Astro, theming with CSS variables, new 2025-2026 components, customization patterns, registry system, and comparison with Radix/MUI/Ant Design.
shadcn/ui isn’t a component library in the traditional sense — you don’t install it as an npm package and import from it. Instead, you copy the components directly into your project. They become yours. You read them, modify them, and own them completely.
This unconventional approach turned out to be exactly what the React ecosystem needed. No more fighting library authors over customization. No more version lock-in. No more wondering what that component does internally. In 2026, shadcn/ui is the default starting point for serious React projects.
What Makes shadcn/ui Different
Traditional component libraries (MUI, Ant Design, Chakra):
- Installed as
node_modulespackage - You import components and pass props
- Customization happens through theme objects,
sxprops, or CSS overrides - You’re at the mercy of the library’s API design choices
- Bundle includes all components, even unused ones
shadcn/ui:
- Components are copied into your
src/components/ui/directory - Built on Radix UI primitives (accessibility for free) + Tailwind CSS
- Complete source code in your repo — no black boxes
- Customize by editing the file directly
- Bundle only includes components you actually added
# You don't install shadcn as a dependency
# You use the CLI to copy components into your project
npx shadcn@latest add button
# Creates: src/components/ui/button.tsx
Installation
Next.js (App Router)
# New project
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
# Initialize shadcn
npx shadcn@latest init
The init command asks:
- Which style? (Default / New York)
- Which base color? (Slate / Gray / Zinc / Neutral / Stone)
- Where is your global CSS file?
- Use CSS variables for theming? (yes)
- Where is your tailwind.config.js?
After init, your project gets:
src/
├── components/
│ └── ui/ # Components land here
├── lib/
│ └── utils.ts # cn() utility
tailwind.config.ts # Updated with shadcn theme tokens
src/app/globals.css # CSS variables added
Vite + React
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
# Configure path alias in vite.config.ts
npm install -D @types/node
# Then init shadcn
npx shadcn@latest init
You’ll need to configure path aliases:
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
// tsconfig.json — add paths
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Remix
npx create-remix@latest my-app
cd my-app
npx shadcn@latest init
Remix works well with shadcn. The main consideration: server-side rendering means your components render on the server first, so avoid browser-only APIs in component code.
Astro
npm create astro@latest my-site
npx astro add react tailwind
npx shadcn@latest init
In Astro, shadcn components work in .astro files via the React integration, or in standalone .tsx files. Remember to add client:load (or client:visible) directives when interactivity is needed:
---
import { Button } from "@/components/ui/button";
import { Dialog } from "@/components/ui/dialog";
---
<!-- Static button — no interactivity needed -->
<Button>Click me</Button>
<!-- Dialog needs client-side JS -->
<Dialog client:load>
<!-- dialog content -->
</Dialog>
Adding Components
# Single component
npx shadcn@latest add button
# Multiple at once
npx shadcn@latest add button input label card
# Interactive browser (see all available)
npx shadcn@latest add
Each component pulls in its dependencies automatically. dialog also adds button if you don’t have it. Components are placed in src/components/ui/.
Core Components
# Form primitives
npx shadcn@latest add button input textarea label select checkbox radio-group switch
# Layout
npx shadcn@latest add card separator sheet sidebar
# Overlays
npx shadcn@latest add dialog drawer alert-dialog popover tooltip
# Navigation
npx shadcn@latest add navigation-menu tabs breadcrumb
# Data display
npx shadcn@latest add table badge avatar progress skeleton
# Feedback
npx shadcn@latest add alert toast sonner
# Forms (with react-hook-form + zod)
npx shadcn@latest add form
Theming with CSS Variables
shadcn’s theming system uses CSS custom properties, making it framework-agnostic and easy to customize.
The Variable System
/* globals.css */
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
/* ... dark mode overrides */
}
Values use h s% l% format (HSL without the hsl() wrapper) so Tailwind can compose opacity modifiers: bg-primary/80 becomes hsl(var(--primary) / 0.8).
Creating a Custom Theme
To brand shadcn for your product, override the CSS variables:
/* Your brand theme */
:root {
--primary: 262.1 83.3% 57.8%; /* Purple */
--primary-foreground: 210 20% 98%;
--secondary: 220 14.3% 95.9%;
--secondary-foreground: 220.9 39.3% 11%;
--accent: 262.1 83.3% 95%;
--accent-foreground: 262.1 83.3% 30%;
--radius: 0.75rem; /* More rounded */
}
The theme generator at ui.shadcn.com/themes lets you preview and export themes visually.
Dark Mode
// With next-themes
import { ThemeProvider } from "next-themes";
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
}
// Theme toggle
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
import { Moon, Sun } from "lucide-react";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</Button>
);
}
New Components in 2025–2026
Sidebar (2025)
The most requested component. A complete sidebar system with collapsible, icon-only mode, nested items, and keyboard navigation:
npx shadcn@latest add sidebar
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
export function AppSidebar() {
return (
<SidebarProvider>
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href="/dashboard">
<LayoutDashboard />
<span>Dashboard</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<main>
<SidebarTrigger />
{/* page content */}
</main>
</SidebarProvider>
);
}
Chart (2025)
Built on Recharts, with consistent theming and a clean API:
npx shadcn@latest add chart
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
const chartData = [
{ month: "Jan", desktop: 186, mobile: 80 },
{ month: "Feb", desktop: 305, mobile: 200 },
// ...
];
const chartConfig = {
desktop: { label: "Desktop", color: "hsl(var(--chart-1))" },
mobile: { label: "Mobile", color: "hsl(var(--chart-2))" },
};
export function BarChartComponent() {
return (
<ChartContainer config={chartConfig}>
<BarChart data={chartData}>
<CartesianGrid vertical={false} />
<XAxis dataKey="month" />
<ChartTooltip content={<ChartTooltipContent />} />
<Bar dataKey="desktop" fill="var(--color-desktop)" />
<Bar dataKey="mobile" fill="var(--color-mobile)" />
</BarChart>
</ChartContainer>
);
}
Sonner (Toast replacement)
sonner replaces the original toast component with a cleaner API:
npx shadcn@latest add sonner
import { Toaster } from "@/components/ui/sonner";
import { toast } from "sonner";
// In your layout
<Toaster position="bottom-right" />
// Anywhere in your app
toast.success("Post created!");
toast.error("Failed to save", { description: "Network error" });
toast.promise(savePost(), {
loading: "Saving...",
success: "Saved!",
error: "Failed to save",
});
Customization Patterns
Extending a Component
The beauty of shadcn: open the file and edit it.
// src/components/ui/button.tsx — the generated file
// Add a new variant directly:
const buttonVariants = cva(
"inline-flex items-center justify-center ...",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground ...",
destructive: "bg-destructive text-destructive-foreground ...",
outline: "border border-input bg-background ...",
secondary: "bg-secondary text-secondary-foreground ...",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
// Add your custom variant:
brand: "bg-violet-600 text-white hover:bg-violet-700 shadow-lg",
success: "bg-green-600 text-white hover:bg-green-700",
},
// ...
},
}
);
Building Composite Components
Use shadcn primitives as building blocks:
// src/components/ui/stat-card.tsx
import { Card, CardContent, CardHeader, CardTitle } from "./card";
import { Badge } from "./badge";
import { TrendingUp, TrendingDown } from "lucide-react";
interface StatCardProps {
title: string;
value: string;
change: number;
changeLabel: string;
}
export function StatCard({ title, value, change, changeLabel }: StatCardProps) {
const isPositive = change >= 0;
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{title}</CardTitle>
<Badge variant={isPositive ? "default" : "destructive"}>
{isPositive ? <TrendingUp className="h-3 w-3 mr-1" /> : <TrendingDown className="h-3 w-3 mr-1" />}
{Math.abs(change)}%
</Badge>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{value}</div>
<p className="text-xs text-muted-foreground">{changeLabel}</p>
</CardContent>
</Card>
);
}
Forms with react-hook-form + Zod
shadcn’s form component wraps react-hook-form with accessible label/error associations:
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, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
const formSchema = z.object({
email: z.string().email("Invalid email address"),
name: z.string().min(2, "Name must be at least 2 characters"),
});
export function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: { email: "", name: "" },
});
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values);
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="[email protected]" {...field} />
</FormControl>
<FormDescription>Your primary email address.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Save changes</Button>
</form>
</Form>
);
}
The Registry System
Vite 6 enabled a new feature in shadcn: the registry system. You can publish your own component library using the shadcn format — your components, your team’s design system, distributed the same way shadcn distributes its own components.
// registry.json — your component registry
{
"name": "my-company-ui",
"homepage": "https://ui.mycompany.com",
"items": [
{
"name": "stat-card",
"type": "registry:ui",
"dependencies": ["@/components/ui/card", "@/components/ui/badge"],
"files": ["components/ui/stat-card.tsx"]
}
]
}
Team members install your components the same way they’d install official shadcn components:
npx shadcn@latest add https://ui.mycompany.com/r/stat-card.json
This is how large organizations are building internal design systems in 2026: use shadcn’s distribution mechanism for their own components without maintaining a separate npm package.
shadcn/ui vs Radix UI vs MUI vs Ant Design
Radix UI
shadcn is built ON Radix UI. Radix provides the unstyled, accessible primitives; shadcn adds Tailwind styling. You can use Radix directly if you want complete style control, but you’re responsible for all styling:
// Raw Radix — you write all the CSS
import * as Dialog from "@radix-ui/react-dialog";
<Dialog.Root>
<Dialog.Overlay className="your-overlay-styles" />
<Dialog.Content className="your-content-styles">
...
</Dialog.Content>
</Dialog.Root>
Use Radix when: you have a custom design system with no Tailwind, or you’re building a component library that needs to be style-agnostic.
Material UI (MUI)
MUI is battle-tested and comprehensive. 80+ components, excellent TypeScript support, and the Material Design visual language baked in. The cost: bundle size (~300KB), theming complexity, and the sx prop system that fights Tailwind.
Use MUI when: your design requirements align with Material Design, your team already knows it, or you need the comprehensive component coverage (DataGrid, DatePicker, etc.).
Ant Design
Ant Design is the enterprise choice, especially in Asian tech companies. Comprehensive, opinionated, and with enterprise-specific components (virtual tables, complex forms, tree views). The visual style is distinctly “enterprise” and takes significant effort to rebrand.
Use Ant Design when: you’re building enterprise software with complex data tables and forms, or your target users expect enterprise UX patterns.
shadcn/ui
Use shadcn when: you want full code ownership, Tailwind-first styling, modern React patterns, and a design that you can make distinctly yours.
Performance Considerations
shadcn components are code that lives in your project — you’re responsible for their performance. Key considerations:
Tree-shaking: since shadcn components are local files, they’re always tree-shaken correctly. Unused components aren’t in your bundle.
Radix UI bundle size: each Radix primitive (Dialog, Popover, etc.) adds to your bundle. Be deliberate about which components you add. A simple tooltip doesn’t need the full Dialog primitive.
Tailwind purging: ensure your tailwind.config.ts content array includes your UI components:
content: [
"./src/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
],
Conclusion
shadcn/ui solved a real problem: the tension between needing production-ready components and needing the ability to fully customize them. By making component source code the distribution unit, it gives you both.
The 2025–2026 additions (Sidebar, Chart, improved theming, Registry) mature it from a clever experiment into a full design system foundation. The ecosystem around it — the theme generators, the block libraries, the CLI tooling — reflects genuine community investment.
For new React projects in 2026: start with shadcn. Add components as you need them. Build a small internal component library on top. Use the Registry when your team grows. By the time you need the advanced enterprise components that MUI offers, you’ll know exactly what custom components you’ve built and what gaps remain.
Related tools on DevPlaybook:
- Tailwind Color Palette Generator — generate Tailwind color scales
- CSS Variables Inspector — debug CSS custom properties
- shadcn Theme Generator — create shadcn themes visually
50 CLI Commands That Save Hours Every Week
The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.
- Git mastery (10 cmds)
- File & search wizardry
- Docker & DevOps tricks
- Process & network tools
- Productivity boosters
- macOS & Linux install guide
No spam. 1 email/week max. Unsubscribe anytime.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links — we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.