--- name: shadcn-ui description: shadcn/ui component patterns with Radix primitives and Tailwind styling. Use when building UI components, using CVA variants, implementing compound components, or styling with data-slot attributes. Triggers on shadcn, cva, cn(), data-slot, Radix, Button, Card, Dialog, VariantProps. --- # shadcn/ui Component Development ## Contents - [CLI Commands](#cli-commands) - Installing and adding components - [Quick Reference](#quick-reference) - cn(), basic CVA pattern - [Component Anatomy](#component-anatomy) - Props typing, asChild, data-slot - [Component Patterns](#component-patterns) - Compound components - [Styling Techniques](#styling-techniques) - CVA variants, modern CSS selectors, accessibility states - [Decision Tables](#decision-tables) - When to use CVA, compound components, asChild, Context - [Common Patterns](#common-patterns) - Form elements, dialogs, sidebars - [Reference Files](#reference-files) - Full implementations and advanced patterns ## CLI Commands ### Initialize shadcn/ui ```bash npx shadcn@latest init ``` This creates a `components.json` configuration file and sets up: - Tailwind CSS configuration - CSS variables for theming - cn() utility function - Required dependencies ### Add Components ```bash # Add a single component npx shadcn@latest add button # Add multiple components npx shadcn@latest add button card dialog # Add all available components npx shadcn@latest add --all ``` **Important:** The package name changed in 2024: - Old (deprecated): `npx shadcn-ui@latest add` - Current: `npx shadcn@latest add` ### Common Options - `-y, --yes` - Skip confirmation prompt - `-o, --overwrite` - Overwrite existing files - `-c, --cwd ` - Set working directory - `--src-dir` - Use src directory structure ## Quick Reference ### cn() Utility ```tsx import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } ``` ### Basic CVA Pattern ```tsx import { cva, type VariantProps } from "class-variance-authority" const buttonVariants = cva( "base-classes-applied-to-all-variants", { variants: { variant: { default: "bg-primary text-primary-foreground", outline: "border bg-background", }, size: { sm: "h-8 px-3", lg: "h-10 px-6", }, }, defaultVariants: { variant: "default", size: "sm", }, } ) function Button({ variant, size, className, ...props }: React.ComponentProps<"button"> & VariantProps) { return ( // Renders // Renders with button styling // Works with Next.js Link ``` ### data-slot Attributes Every component includes `data-slot` for CSS targeting: ```tsx function Card({ ...props }) { return
} function CardHeader({ ...props }) { return
} ``` **CSS/Tailwind targeting:** ```css [data-slot="button"] { /* styles */ } [data-slot="card"] [data-slot="button"] { /* nested targeting */ } ``` ```tsx
``` **Conditional layouts with has():** ```tsx
``` ## Component Patterns ### Compound Components ```tsx export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } function Card({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardHeader({ className, ...props }: React.ComponentProps<"div">) { return
} function CardTitle({ className, ...props }: React.ComponentProps<"div">) { return
} ``` ## Styling Techniques ### CVA Variants **Multiple dimensions:** ```tsx const buttonVariants = cva("base-classes", { variants: { variant: { default: "bg-primary text-primary-foreground", destructive: "bg-destructive text-white", outline: "border bg-background", ghost: "hover:bg-accent", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2", sm: "h-8 px-3", lg: "h-10 px-6", icon: "size-9", }, }, defaultVariants: { variant: "default", size: "default" }, }) ``` **Compound variants:** ```tsx compoundVariants: [ { variant: "outline", size: "lg", class: "border-2" }, ] ``` **Type extraction:** ```tsx type ButtonVariants = VariantProps // Result: { variant?: "default" | "outline" | ..., size?: "sm" | "lg" | ... } ``` ### Modern CSS Selectors in Tailwind **has() selector:** ```tsx
Styled when sibling active
``` **Container queries:** ```tsx
Responds to container width
``` ### Accessibility States ```tsx className={cn( // Focus "outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", // Invalid "aria-invalid:border-destructive aria-invalid:ring-destructive/20", // Disabled "disabled:pointer-events-none disabled:opacity-50", )} Close // Screen reader only ``` ### Dark Mode Semantic tokens adapt automatically: ```tsx className="bg-background text-foreground dark:bg-input/30 dark:hover:bg-input/50" ``` Tokens: `bg-background`, `text-foreground`, `bg-primary`, `text-primary-foreground`, `bg-card`, `text-card-foreground`, `border-input`, `text-muted-foreground` ## Decision Tables ### When to Use CVA | Scenario | Use CVA | Alternative | |----------|---------|-------------| | Multiple visual variants (primary, outline, ghost) | Yes | Plain className | | Size variations (sm, md, lg) | Yes | Plain className | | Compound conditions (outline + large = thick border) | Yes | Conditional cn() | | One-off custom styling | No | className prop | | Dynamic colors from props | No | Inline styles or CSS variables | ### When to Use Compound Components | Scenario | Use Compound | Alternative | |----------|--------------|-------------| | Complex UI with multiple semantic parts | Yes | Single component with many props | | Optional sections (header, footer) | Yes | Boolean show/hide props | | Different styling for each part | Yes | CSS selectors | | Shared state between parts | Yes + Context | Props drilling | | Simple wrapper with children | No | Single component | ### When to Use asChild | Scenario | Use asChild | Alternative | |----------|-------------|-------------| | Component should work as link or button | Yes | Duplicate component | | Need button styles on custom element | Yes | Export variant styles | | Integration with routing libraries | Yes | Wrapper components | | Always renders same element | No | Standard component | ### When to Use Context | Scenario | Use Context | Alternative | |----------|-------------|-------------| | Deep prop drilling (>3 levels) | Yes | Props | | State shared by many siblings | Yes | Lift state up | | Plugin/extension architecture | Yes | Props | | Simple parent-child communication | No | Props | ## Common Patterns ### Form Input ```tsx function Input({ className, type, ...props }: React.ComponentProps<"input">) { return ( ) } ``` ### Dialog Content ```tsx function DialogContent({ children, showCloseButton = true, ...props }) { return ( {children} {showCloseButton && ( Close )} ) } ``` ### Sidebar with Context ```tsx function SidebarProvider({ defaultOpen = true, children }) { const isMobile = useIsMobile() const [open, setOpen] = React.useState(defaultOpen) React.useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "b" && (e.metaKey || e.ctrlKey)) { e.preventDefault() setOpen(o => !o) } } window.addEventListener("keydown", handleKeyDown) return () => window.removeEventListener("keydown", handleKeyDown) }, []) const contextValue = React.useMemo( () => ({ state: open ? "expanded" : "collapsed", open, setOpen, isMobile }), [open, setOpen, isMobile] ) return (
{children}
) } ``` ## Reference Files For comprehensive examples and advanced patterns: - **[components.md](./references/components.md)** - Full implementations: Button, Card, Badge, Input, Label, Textarea, Dialog - **[cva.md](./references/cva.md)** - CVA patterns: compound variants, responsive variants, type extraction - **[patterns.md](./references/patterns.md)** - Architectural patterns: compound components, asChild, controlled state, Context, data-slot, has() selectors