--- name: shadcn-ui description: Provides complete shadcn/ui component library patterns 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. allowed-tools: Read, Write, Bash, Edit, Glob --- # shadcn/ui Component Patterns ## Overview Expert guide for building accessible, customizable UI components with shadcn/ui, Radix UI, and Tailwind CSS. This skill provides comprehensive patterns for implementing production-ready components with full accessibility support. > **Mosaic Stack Note:** This skill's Tailwind configuration examples reference v3 patterns (`tailwind.config.js`, `@tailwind` directives, `require()` plugins). Mosaic Stack uses **Tailwind CSS v4** which uses CSS-native `@import "tailwindcss"` and `@theme` blocks instead. Component usage patterns and Radix UI primitives remain accurate regardless of Tailwind version. For Tailwind v4 patterns, also load the `tailwind-design-system` skill. ## 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) - [Charts](#charts-component) - [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 ## Instructions 1. **Initialize Project**: Run `npx shadcn@latest init` to configure shadcn/ui 2. **Install Components**: Add components with `npx shadcn@latest add ` 3. **Configure Theme**: Customize CSS variables in globals.css for theming 4. **Import Components**: Use components from `@/components/ui/` directory 5. **Customize as Needed**: Modify component code directly in your project 6. **Add Form Validation**: Integrate React Hook Form with Zod schemas 7. **Test Accessibility**: Verify ARIA attributes and keyboard navigation ## Examples ### Complete Form with Validation ```tsx "use client" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" 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 formSchema = z.object({ email: z.string().email("Invalid email"), password: z.string().min(8, "Password must be at least 8 characters"), }) export function LoginForm() { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { email: "", password: "" }, }) return (
( Email )} /> ) } ``` ## Constraints and Warnings - **Not an NPM Package**: Components are copied to your project; you own the code - **Client Components**: Most components require "use client" directive - **Radix Dependencies**: Ensure all @radix-ui packages are installed - **Tailwind Required**: Components rely on Tailwind CSS utilities - **TypeScript**: Designed for TypeScript projects; type definitions included - **Path Aliases**: Configure @ alias in tsconfig.json for imports - **Dark Mode**: Set up dark mode with CSS variables or class strategy ## 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} ))}
) } ``` ### Charts Component Installation: ```bash npx shadcn@latest add chart ``` The charts component in shadcn/ui is built on **Recharts** - providing direct access to all Recharts capabilities with consistent theming and styling. #### ChartContainer and ChartConfig The `ChartContainer` wraps your Recharts component and accepts a `config` prop for theming: ```tsx import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts" import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart" const chartConfig = { desktop: { label: "Desktop", color: "var(--chart-1)", }, mobile: { label: "Mobile", color: "var(--chart-2)", }, } satisfies import("@/components/ui/chart").ChartConfig const chartData = [ { month: "January", desktop: 186, mobile: 80 }, { month: "February", desktop: 305, mobile: 200 }, { month: "March", desktop: 237, mobile: 120 }, ] export function BarChartDemo() { return ( value.slice(0, 3)} /> } /> ) } ``` #### ChartConfig with Custom Colors You can define custom colors directly in the configuration: ```tsx const chartConfig = { visitors: { label: "Visitors", color: "#2563eb", // Custom hex color theme: { light: "#2563eb", dark: "#60a5fa", }, }, sales: { label: "Sales", color: "var(--chart-1)", // CSS variable theme: { light: "oklch(0.646 0.222 41.116)", dark: "oklch(0.696 0.182 281.41)", }, }, } satisfies import("@/components/ui/chart").ChartConfig ``` #### CSS Variables for Charts Add chart color variables to your `globals.css`: ```css @layer base { :root { /* Chart colors */ --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.546 0.198 38.228); --chart-4: oklch(0.596 0.151 343.253); --chart-5: oklch(0.546 0.158 49.157); } .dark { --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.698 0.141 24.311); --chart-4: oklch(0.676 0.172 171.196); --chart-5: oklch(0.578 0.192 302.85); } } ``` #### Line Chart Example ```tsx import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts" import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart" const chartConfig = { price: { label: "Price", color: "var(--chart-1)", }, } satisfies import("@/components/ui/chart").ChartConfig const chartData = [ { month: "January", price: 186 }, { month: "February", price: 305 }, { month: "March", price: 237 }, { month: "April", price: 203 }, { month: "May", price: 276 }, ] export function LineChartDemo() { return ( `$${value}`} /> } /> ) } ``` #### Area Chart Example ```tsx import { Area, AreaChart, XAxis, YAxis } from "recharts" import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltipContent } from "@/components/ui/chart" const chartConfig = { desktop: { label: "Desktop", color: "var(--chart-1)" }, mobile: { label: "Mobile", color: "var(--chart-2)" }, } satisfies import("@/components/ui/chart").ChartConfig export function AreaChartDemo() { return ( } /> } /> ) } ``` #### Pie Chart Example ```tsx import { Pie, PieChart } from "recharts" import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltipContent } from "@/components/ui/chart" const chartConfig = { chrome: { label: "Chrome", color: "var(--chart-1)" }, safari: { label: "Safari", color: "var(--chart-2)" }, firefox: { label: "Firefox", color: "var(--chart-3)" }, } satisfies import("@/components/ui/chart").ChartConfig const pieData = [ { browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" }, { browser: "Safari", visitors: 200, fill: "var(--color-safari)" }, { browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" }, ] export function PieChartDemo() { return ( } /> } /> ) } ``` #### ChartTooltipContent Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `labelKey` | string | "label" | Key for tooltip label | | `nameKey` | string | "name" | Key for tooltip name | | `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style | | `hideLabel` | boolean | false | Hide label | | `hideIndicator` | boolean | false | Hide indicator | #### Accessibility Enable keyboard navigation and screen reader support: ```tsx ... ``` This adds: - Keyboard arrow key navigation - ARIA labels for chart elements - Screen reader announcements for data values ## 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