--- name: shadcn-ui description: Complete shadcn/ui component library guide including installation, configuration, and implementation of accessible React components. Use when setting up shadcn/ui, installing components, building forms with React Hook Form and Zod, customizing themes with Tailwind CSS, or implementing UI patterns like buttons, dialogs, dropdowns, tables, and complex form layouts. language: typescript,tsx framework: react,nextjs,tailwindcss license: MIT allowed-tools: Read, Write, Bash, Edit, Glob --- # shadcn/ui Component Patterns Expert guide for building accessible, customizable UI components with shadcn/ui, Radix UI, and Tailwind CSS. ## Table of Contents - [When to Use](#when-to-use) - [Quick Start](#quick-start) - [Installation & Setup](#installation--setup) - [Project Configuration](#project-configuration) - [Core Components](#core-components) - [Button](#button-component) - [Input & Form Fields](#input--form-fields) - [Forms with Validation](#forms-with-validation) - [Card](#card-component) - [Dialog (Modal)](#dialog-modal-component) - [Select (Dropdown)](#select-dropdown-component) - [Sheet (Slide-over)](#sheet-slide-over-component) - [Menubar & Navigation](#menubar--navigation) - [Table](#table-component) - [Toast Notifications](#toast-notifications) - [Advanced Patterns](#advanced-patterns) - [Customization](#customization) - [Next.js Integration](#nextjs-integration) - [Best Practices](#best-practices) - [Common Component Combinations](#common-component-combinations) ## When to Use - Setting up a new project with shadcn/ui - Installing or configuring individual components - Building forms with React Hook Form and Zod validation - Creating accessible UI components (buttons, dialogs, dropdowns, sheets) - Customizing component styling with Tailwind CSS - Implementing design systems with shadcn/ui - Building Next.js applications with TypeScript - Creating complex layouts and data displays ## Quick Start For new projects, use the automated setup: ```bash # Create Next.js project with shadcn/ui npx create-next-app@latest my-app --typescript --tailwind --eslint --app cd my-app npx shadcn@latest init # Install essential components npx shadcn@latest add button input form card dialog select ``` For existing projects: ```bash # Install dependencies npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react # Initialize shadcn/ui npx shadcn@latest init ``` ## What is shadcn/ui? shadcn/ui is **not** a traditional component library or npm package. Instead: - It's a **collection of reusable components** that you can copy into your project - Components are **yours to customize** - you own the code - Built with **Radix UI** primitives for accessibility - Styled with **Tailwind CSS** utilities - Includes CLI tool for easy component installation ## Installation & Setup ### Initial Setup ```bash # Initialize shadcn/ui in your project npx shadcn@latest init ``` During setup, you'll configure: - TypeScript or JavaScript - Style (Default, New York, etc.) - Base color theme - CSS variables or Tailwind CSS classes - Component installation path ### Installing Individual Components ```bash # Install a single component npx shadcn@latest add button # Install multiple components npx shadcn@latest add button input form # Install all components npx shadcn@latest add --all ``` ### Manual Installation If you prefer manual setup: ```bash # Install dependencies for a specific component npm install @radix-ui/react-slot # Copy component code from ui.shadcn.com # Place in src/components/ui/ ``` ## Project Configuration ### Required Dependencies ```json { "dependencies": { "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "lucide-react": "^0.294.0", "tailwind-merge": "^2.0.0", "tailwindcss-animate": "^1.0.7" } } ``` ### TSConfig Configuration ```json { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "es6"], "allowJs": true, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } ], "baseUrl": ".", "paths": { "@/components/*": ["./src/components/*"], "@/lib/*": ["./src/lib/*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } ``` ### Tailwind Configuration ```js // tailwind.config.js /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: ["class"], content: [ './pages/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}', './src/**/*.{ts,tsx}', ], prefix: "", theme: { container: { center: true, padding: "2rem", screens: { "2xl": "1400px", }, }, extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))", }, destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))", }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))", }, accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))", }, popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))", }, card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, keyframes: { "accordion-down": { from: { height: "0" }, to: { height: "var(--radix-accordion-content-height)" }, }, "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, }, }, plugins: [require("tailwindcss-animate")], } ``` ### CSS Variables (globals.css) ```css @tailwind base; @tailwind components; @tailwind utilities; @layer base { :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%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; --primary: 210 40% 98%; --primary-foreground: 222.2 47.4% 11.2%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 212.7 26.8% 83.9%; } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } } ``` ## Core Components ### Button Component Installation: ```bash npx shadcn@latest add button ``` Basic usage: ```tsx import { Button } from "@/components/ui/button"; export function ButtonDemo() { return ; } ``` Button variants: ```tsx import { Button } from "@/components/ui/button"; export function ButtonVariants() { return (
); } ``` Button sizes: ```tsx
``` With loading state: ```tsx import { Button } from "@/components/ui/button"; import { Loader2 } from "lucide-react"; export function ButtonLoading() { return ( ); } ``` ### Input & Form Fields #### Input Component Installation: ```bash npx shadcn@latest add input ``` Basic input: ```tsx import { Input } from "@/components/ui/input"; export function InputDemo() { return ; } ``` Input with label: ```tsx import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; export function InputWithLabel() { return (
); } ``` Input with button: ```tsx import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; export function InputWithButton() { return (
); } ``` ### Forms with Validation Installation: ```bash npx shadcn@latest add form ``` This installs React Hook Form, Zod, and form components. Complete form example: ```tsx "use client" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as 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" import { toast } from "@/components/ui/use-toast" const formSchema = z.object({ username: z.string().min(2, { message: "Username must be at least 2 characters.", }), email: z.string().email({ message: "Please enter a valid email address.", }), }) export function ProfileForm() { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { username: "", email: "", }, }) function onSubmit(values: z.infer) { toast({ title: "You submitted the following values:", description: (
          {JSON.stringify(values, null, 2)}
        
), }) } return (
( Username This is your public display name. )} /> ( Email )} /> ) } ``` ### Card Component Installation: ```bash npx shadcn@latest add card ``` Basic card: ```tsx import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card" export function CardDemo() { return ( Card Title Card Description

Card Content

Card Footer

) } ``` Card with form: ```tsx import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" export function CardWithForm() { return ( Create project Deploy your new project in one-click.
) } ``` ### Dialog (Modal) Component Installation: ```bash npx shadcn@latest add dialog ``` Basic dialog: ```tsx import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" export function DialogDemo() { return ( Edit profile Make changes to your profile here. Click save when you're done.
) } ``` ### Sheet (Slide-over) Component Installation: ```bash npx shadcn@latest add sheet ``` Basic sheet: ```tsx import { Button } from "@/components/ui/button" import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet" export function SheetDemo() { return ( Edit profile Make changes to your profile here. Click save when you're done.
) } ``` Sheet with side placement: ```tsx Settings Configure your application settings here. {/* Settings content */} ``` ### Menubar & Navigation #### Menubar Component Installation: ```bash npx shadcn@latest add menubar ``` Basic menubar: ```tsx import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, } from "@/components/ui/menubar" export function MenubarDemo() { return ( File New Tab ⌘T New Window ⌘N Share Print Edit Undo ⌘Z Redo ⌘Y Find Search the web Find... Find Next Find Previous ) } ``` ### Select (Dropdown) Component Installation: ```bash npx shadcn@latest add select ``` Basic select: ```tsx import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" export function SelectDemo() { return ( ) } ``` Select in form: ```tsx ( Role )} /> ``` ### Toast Notifications Installation: ```bash npx shadcn@latest add toast ``` Setup toast provider in root layout: ```tsx import { Toaster } from "@/components/ui/toaster" export default function RootLayout({ children }) { return ( {children} ) } ``` Using toast: ```tsx import { useToast } from "@/components/ui/use-toast" import { Button } from "@/components/ui/button" export function ToastDemo() { const { toast } = useToast() return ( ) } ``` Toast variants: ```tsx // Success toast({ title: "Success", description: "Your changes have been saved.", }) // Error toast({ variant: "destructive", title: "Error", description: "Something went wrong.", }) // With action toast({ title: "Uh oh! Something went wrong.", description: "There was a problem with your request.", action: Try again, }) ``` ### Table Component Installation: ```bash npx shadcn@latest add table ``` Basic table: ```tsx import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" const invoices = [ { invoice: "INV001", status: "Paid", method: "Credit Card", amount: "$250.00" }, { invoice: "INV002", status: "Pending", method: "PayPal", amount: "$150.00" }, ] export function TableDemo() { return ( A list of your recent invoices. Invoice Status Method Amount {invoices.map((invoice) => ( {invoice.invoice} {invoice.status} {invoice.method} {invoice.amount} ))}
) } ``` ## Customization ### Theming with CSS Variables shadcn/ui uses CSS variables for theming. Configure in `globals.css`: ```css @layer base { :root { --background: 0 0% 100%; --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%; --primary: 210 40% 98%; --primary-foreground: 222.2 47.4% 11.2%; /* ... other dark mode variables */ } } ``` ### Customizing Components Since you own the code, customize directly: ```tsx // components/ui/button.tsx import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent", // Add custom variant custom: "bg-gradient-to-r from-purple-500 to-pink-500 text-white", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", // Add custom size xl: "h-14 rounded-md px-10 text-lg", }, }, defaultVariants: { variant: "default", size: "default", }, } ) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button" return ( ) } ) Button.displayName = "Button" export { Button, buttonVariants } ``` ## Next.js Integration ### App Router Setup For Next.js 13+ with App Router, ensure components use `"use client"` directive: ```tsx // src/components/ui/button.tsx "use client" import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" // ... rest of component ``` ### Layout Integration Add the Toaster to your root layout: ```tsx // app/layout.tsx import { Toaster } from "@/components/ui/toaster" import "./globals.css" export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } ``` ### Server Components When using shadcn/ui components in Server Components, wrap them in a Client Component: ```tsx // app/dashboard/page.tsx import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { ButtonClient } from "@/components/ui/button-client" export default function DashboardPage() { return (
Dashboard Interactive Button
) } ``` ```tsx // src/components/ui/button-client.tsx "use client" import { Button } from "./button" export function ButtonClient(props: React.ComponentProps) { return