--- name: shadcn-ui description: Guide for implementing shadcn/ui - a collection of beautifully-designed, accessible UI components built with Radix UI and Tailwind CSS. Use when building user interfaces, adding UI components, or implementing design systems in React-based applications. license: MIT version: 1.0.0 --- # shadcn/ui Skill shadcn/ui is a collection of beautifully-designed, accessible components and a code distribution platform built with TypeScript, Tailwind CSS, and Radix UI primitives. It's not a traditional component library but a collection of reusable components you can copy and paste into your apps. ## Reference https://ui.shadcn.com/llms.txt ## When to Use This Skill Use this skill when: - Building user interfaces with React-based frameworks (Next.js, Vite, Remix, Astro, etc.) - Adding pre-built, accessible UI components to applications - Implementing design systems with Tailwind CSS - Setting up forms with validation (React Hook Form + Zod) - Adding data tables, charts, or complex UI patterns - Implementing dark mode with consistent theming - Customizing component appearance and behavior ## Core Concepts ### Key Principles - **Open Code**: Copy components into your project, modify freely - **Composition**: Built with composable primitives from Radix UI - **Distribution**: Components distributed via CLI, not npm packages - **Beautiful Defaults**: Thoughtfully designed with excellent aesthetics - **AI-Ready**: Structured for easy integration with AI tools ### Architecture shadcn/ui follows a unique distribution model: 1. **CLI Tool**: Installs and manages components via `npx shadcn@latest` 2. **Component Registry**: Central repository of components 3. **Local Components**: Components live in your `components/ui/` directory 4. **Full Ownership**: You own the code, modify as needed ### Technology Stack - **TypeScript**: Full type safety - **Tailwind CSS**: Utility-first styling (v3 and v4 support) - **Radix UI**: Accessible, unstyled primitives - **Class Variance Authority**: Component variants - **React 19**: Compatible with latest React ## Installation & Setup ### Initial Setup **Using the CLI (Recommended):** ```bash npx shadcn@latest init ``` The CLI will prompt for: - Framework preference (Next.js, Vite, etc.) - TypeScript or JavaScript - Component installation location - CSS variables or Tailwind configuration - Color theme preferences - Global CSS file location **Manual Setup:** 1. Install dependencies: ```bash npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react ``` 2. Create `components.json`: ```json { "$schema": "https://ui.shadcn.com/schema.json", "style": "new-york", "rsc": true, "tsx": true, "tailwind": { "config": "tailwind.config.ts", "css": "app/globals.css", "baseColor": "zinc", "cssVariables": true }, "aliases": { "components": "@/components", "utils": "@/lib/utils" } } ``` 3. Configure Tailwind: ```ts // tailwind.config.ts import type { Config } from "tailwindcss" const config: Config = { darkMode: ["class"], content: [ './pages/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}', ], theme: { extend: {}, }, plugins: [require("tailwindcss-animate")], } export default config ``` 4. Create utility file: ```ts // lib/utils.ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` ### Adding Components **Via CLI:** ```bash # Add single component npx shadcn@latest add button # Add multiple components npx shadcn@latest add button card dialog # Add all components npx shadcn@latest add --all ``` **What happens when you add a component:** 1. Component files are copied to `components/ui/` 2. Dependencies are automatically installed 3. Component is ready to import and use ## Component Categories ### Form & Input Components **Button:** ```tsx import { Button } from "@/components/ui/button" ``` **Input:** ```tsx import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label"
``` **Form (with validation):** ```tsx import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" const formSchema = z.object({ username: z.string().min(2).max(50), email: z.string().email(), }) function ProfileForm() { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { username: "", email: "", }, }) function onSubmit(values: z.infer) { console.log(values) } return (
( Username This is your public display name. )} /> ) } ``` **Select:** ```tsx import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" ``` **Checkbox:** ```tsx import { Checkbox } from "@/components/ui/checkbox" import { Label } from "@/components/ui/label"
``` **Date Picker:** ```tsx import { Calendar } from "@/components/ui/calendar" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" import { Button } from "@/components/ui/button" import { CalendarIcon } from "lucide-react" import { format } from "date-fns" const [date, setDate] = useState() ``` ### Layout & Navigation **Card:** ```tsx import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card" Card Title Card Description

Card Content

Card Footer

``` **Tabs:** ```tsx import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" Account Password Account settings Password settings ``` **Accordion:** ```tsx import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion" Is it accessible? Yes. It adheres to the WAI-ARIA design pattern. ``` **Navigation Menu:** ```tsx import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, } from "@/components/ui/navigation-menu" Item One Link ``` ### Overlays & Dialogs **Dialog:** ```tsx import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" Are you sure? This action cannot be undone. ``` **Drawer:** ```tsx import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, } from "@/components/ui/drawer" Open Are you sure? This action cannot be undone. Cancel ``` **Popover:** ```tsx import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" Open Place content here. ``` **Toast:** ```tsx import { useToast } from "@/hooks/use-toast" import { Button } from "@/components/ui/button" const { toast } = useToast() ``` **Command:** ```tsx import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command" No results found. Calendar Search Emoji Calculator ``` ### Feedback & Status **Alert:** ```tsx import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" Heads up! You can add components to your app using the CLI. Error Your session has expired. Please log in again. ``` **Progress:** ```tsx import { Progress } from "@/components/ui/progress" ``` **Skeleton:** ```tsx import { Skeleton } from "@/components/ui/skeleton"
``` ### Display Components **Table:** ```tsx import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" A list of your recent invoices. Invoice Status Amount INV001 Paid $250.00
``` **Data Table (with sorting/filtering):** ```bash npx shadcn@latest add data-table ``` **Avatar:** ```tsx import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" CN ``` **Badge:** ```tsx import { Badge } from "@/components/ui/badge" Default Secondary Destructive Outline ``` ## Theming & Customization ### Dark Mode Setup **Next.js (App Router):** 1. Install next-themes: ```bash npm install next-themes ``` 2. Create theme provider: ```tsx // components/theme-provider.tsx "use client" import * as React from "react" import { ThemeProvider as NextThemesProvider } from "next-themes" export function ThemeProvider({ children, ...props }: React.ComponentProps) { return {children} } ``` 3. Wrap app with provider: ```tsx // app/layout.tsx import { ThemeProvider } from "@/components/theme-provider" export default function RootLayout({ children }) { return ( {children} ) } ``` 4. Add theme toggle: ```tsx import { Moon, Sun } from "lucide-react" import { useTheme } from "next-themes" import { Button } from "@/components/ui/button" export function ThemeToggle() { const { setTheme, theme } = useTheme() return ( ) } ``` ### Color Customization **Using CSS Variables:** ```css /* globals.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%; /* ... */ } } ``` **Using Tailwind Config:** ```ts // tailwind.config.ts export default { theme: { 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))", }, // ... }, }, }, } ``` ### Component Customization Since components live in your codebase, you can modify them directly: ```tsx // components/ui/button.tsx // Modify variants, add new ones, change styles const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium", { variants: { variant: { default: "bg-primary text-primary-foreground", destructive: "bg-destructive text-destructive-foreground", outline: "border border-input bg-background", // 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", }, } ) ``` ## Advanced Patterns ### Server Actions (Next.js) ```tsx // app/actions.ts "use server" import { z } from "zod" const schema = z.object({ email: z.string().email(), password: z.string().min(8), }) export async function createUser(formData: FormData) { const validatedFields = schema.safeParse({ email: formData.get("email"), password: formData.get("password"), }) if (!validatedFields.success) { return { errors: validatedFields.error.flatten().fieldErrors, } } // Create user } // app/signup/page.tsx import { createUser } from "@/app/actions" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" export default function SignupPage() { return (
) } ``` ### Reusable Form Patterns ```tsx // lib/form-utils.ts import { UseFormReturn } from "react-hook-form" import { FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form" import { Input } from "@/components/ui/input" export function TextFormField({ form, name, label, placeholder, type = "text", }: { form: UseFormReturn name: string label: string placeholder?: string type?: string }) { return ( ( {label} )} /> ) } ``` ### Responsive Component Composition ```tsx import { useMediaQuery } from "@/hooks/use-media-query" import { Dialog, DialogContent } from "@/components/ui/dialog" import { Drawer, DrawerContent } from "@/components/ui/drawer" export function ResponsiveDialog({ children, ...props }) { const isDesktop = useMediaQuery("(min-width: 768px)") if (isDesktop) { return ( {children} ) } return ( {children} ) } ``` ## Best Practices 1. **Use TypeScript**: Leverage full type safety for better DX 2. **Customize Components**: Modify components directly in your codebase 3. **Compose Primitives**: Build complex UIs by composing simple components 4. **Follow Accessibility**: Components are built on accessible Radix UI primitives 5. **Use Form Validation**: Integrate React Hook Form + Zod for robust forms 6. **Dark Mode**: Implement theme switching for better UX 7. **Responsive Design**: Use Tailwind responsive utilities 8. **Performance**: Use code splitting and lazy loading for large component sets 9. **Consistent Spacing**: Use Tailwind spacing scale consistently 10. **Icon Library**: Use lucide-react for consistent icons ## Framework-Specific Setup ### Next.js - Support for App Router and Pages Router - Server Components compatibility - Server Actions integration ### Vite - Fast development with HMR - Easy setup with TypeScript ### Remix - Route-based architecture - Progressive enhancement ### Astro - Static site generation - Islands architecture ### Laravel (Inertia.js) - Backend integration with Laravel - React frontend with Inertia ## Common Patterns ### Loading States ```tsx import { Skeleton } from "@/components/ui/skeleton" export function UserCardSkeleton() { return (
) } export function UserCard({ user }: { user?: User }) { if (!user) return return (
{user.initials}

{user.name}

{user.email}

) } ``` ### Error Handling ```tsx import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { AlertCircle } from "lucide-react" export function ErrorAlert({ error }: { error: Error }) { return ( Error {error.message} ) } ``` ### Confirmation Dialogs ```tsx import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog" export function DeleteConfirmation({ onConfirm }: { onConfirm: () => void }) { return ( Are you absolutely sure? This action cannot be undone. This will permanently delete your account and remove your data from our servers. Cancel Continue ) } ``` ## Troubleshooting ### Common Issues 1. **"Module not found" errors** - Check path aliases in `tsconfig.json` - Verify `components.json` aliases match your project structure - Ensure components are installed in correct directory 2. **Styling not applied** - Verify Tailwind CSS is configured correctly - Check `globals.css` imports CSS variables - Ensure `tailwindcss-animate` plugin is installed 3. **Dark mode not working** - Check ThemeProvider is wrapping your app - Verify `suppressHydrationWarning` on `` tag - Ensure dark mode classes are defined in CSS 4. **Form validation issues** - Install required packages: `react-hook-form`, `@hookform/resolvers`, `zod` - Check schema matches form fields - Verify resolver is configured correctly 5. **TypeScript errors** - Update `@types/react` and `@types/react-dom` - Check component prop types - Ensure TypeScript version is compatible (>= 4.5) ## Resources - Documentation: https://ui.shadcn.com - GitHub: https://github.com/shadcn-ui/ui - Component Registry: https://ui.shadcn.com/docs/components - Examples: https://ui.shadcn.com/examples - Figma Design Kit: https://ui.shadcn.com/figma - v0 (AI UI Generator): https://v0.dev ## Implementation Checklist When implementing shadcn/ui: - [ ] Run `npx shadcn@latest init` to set up project - [ ] Configure Tailwind CSS and path aliases - [ ] Set up dark mode with ThemeProvider - [ ] Install required components via CLI - [ ] Create utility functions (cn helper) - [ ] Set up form handling (React Hook Form + Zod) - [ ] Configure icons (lucide-react) - [ ] Implement theme toggle component - [ ] Test components in both light and dark modes - [ ] Customize color palette if needed - [ ] Add loading states with Skeleton - [ ] Implement error handling patterns - [ ] Test accessibility features - [ ] Optimize bundle size (tree-shaking) - [ ] Add responsive design utilities