--- id: existential-birds/beagle/shadcn-ui version: "67c3ee34" license: Apache-2.0 install: manual updated: 2026-07-21 --- # shadcn-ui — Master shadcn-ui's approach to building reusable components with CVA for variant management, Radix for accessible primitives, and Tailwind for styling. Covers CLI setup, the cn() utility, polymorphic rendering via asChild, compound component structure, and modern CSS selectors like has() for conditional layouts. Publisher: existential-birds · Stars: 74 · Updated: 2026-07-21 Install (manual): `git clone https://github.com/existential-birds/beagle` ## SKILL.md # 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 ### Gates (CLI and file changes) Run these **in order** before `init` / `add` (skip only when you are not running the CLI—e.g. copying snippets from this skill): 1. **Working directory:** **Pass:** `pwd` and `package.json` at that path identify the app root that should receive `components/` and `components.json` (use `-c ` if the shell is elsewhere). 2. **After `init`:** **Pass:** `components.json` exists at that app root (or the documented path for your monorepo layout). 3. **Before `--overwrite`:** **Pass:** you can list which tracked files will be replaced, or version control shows the change is intentional and recoverable. ## 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 [View on SkillFed](https://skillfed.io/existential-birds/beagle/shadcn-ui) · [View on GitHub](https://github.com/existential-birds/beagle)