Killer-Skills Review
Decision support comes first. Repository text comes second.
This page remains useful for operators, but Killer-Skills treats it as reference material instead of a primary organic landing page.
Construye una pagina web profesional en minutos. No necesitas saber programar.
¿Por qué usar esta habilidad?
Construye una pagina web profesional en minutos. No necesitas saber programar.
Mejor para
Suitable for operator workflows that need explicit guardrails before installation and execution.
↓ Casos de uso accionables for shadcn-ui
! Seguridad y limitaciones
Why this page is reference-only
- - Current locale does not satisfy the locale-governance contract.
- - The page lacks a strong recommendation layer.
- - The page lacks concrete use-case guidance.
- - The page lacks explicit limitations or caution signals.
Source Boundary
The section below is supporting source material from the upstream repository. Use the Killer-Skills review above as the primary decision layer.
Browser Sandbox Environment
⚡️ Ready to unleash?
Experience this Agent in a zero-setup browser environment powered by WebContainers. No installation required.
FAQ & Installation Steps
These questions and steps mirror the structured data on this page for better search understanding.
? Frequently Asked Questions
What is shadcn-ui?
Construye una pagina web profesional en minutos. No necesitas saber programar.
How do I install shadcn-ui?
Run the command: npx killer-skills add Hainrixz/claude-webkit/shadcn-ui. It works with Cursor, Windsurf, VS Code, Claude Code, and 19+ other IDEs.
Which IDEs are compatible with shadcn-ui?
This skill is compatible with Cursor, Windsurf, VS Code, Trae, Claude Code, OpenClaw, Aider, Codex, OpenCode, Goose, Cline, Roo Code, Kiro, Augment Code, Continue, GitHub Copilot, Sourcegraph Cody, and Amazon Q Developer. Use the Killer-Skills CLI for universal one-command installation.
↓ How To Install
-
1. Open your terminal
Open the terminal or command line in your project directory.
-
2. Run the install command
Run: npx killer-skills add Hainrixz/claude-webkit/shadcn-ui. The CLI will automatically detect your IDE or AI agent and configure the skill.
-
3. Start using the skill
The skill is now active. Your AI agent can use shadcn-ui immediately in the current project.
! Reference-Only Mode
This page remains useful for installation and reference, but Killer-Skills no longer treats it as a primary indexable landing page. Read the review above before relying on the upstream repository instructions.
Imported Repository Instructions
The section below is supporting source material from the upstream repository. Use the Killer-Skills review above as the primary decision layer.
shadcn-ui
Install shadcn-ui, an AI agent skill for AI agent workflows and automation. Works with Claude Code, Cursor, and Windsurf with one-command setup.
shadcn/ui - Component Library
progressive_disclosure: entry_point: summary, when_to_use, quick_start estimated_tokens: entry: 85 full: 4800
Summary
shadcn/ui is a collection of re-usable React components built with Radix UI primitives and styled with Tailwind CSS. Unlike traditional component libraries, shadcn/ui components are copied directly into your project, giving you full ownership and customization control. Components are accessible, customizable, and open source.
Core Philosophy: Copy-paste components, not npm packages. You own the code.
When to Use
Use shadcn/ui when:
- Building React applications with Tailwind CSS
- Need accessible, production-ready UI components
- Want full control over component code and styling
- Prefer composition over configuration
- Building with Next.js, Vite, Remix, or Astro
- Need dark mode support out of the box
- Want TypeScript-first components
Don't use when:
- Not using Tailwind CSS (core styling dependency)
- Need legacy browser support (uses modern CSS features)
- Prefer packaged npm libraries over code ownership
- Building non-React frameworks (Vue, Svelte, Angular)
Quick Start
Installation
bash1# Initialize shadcn/ui in your project 2npx shadcn-ui@latest init 3 4# Follow interactive prompts: 5# - TypeScript? (yes/no) 6# - Style: Default/New York 7# - Base color: Slate/Gray/Zinc/Neutral/Stone 8# - CSS variables: (yes/no) 9# - React Server Components: (yes/no) 10# - components.json location 11# - Tailwind config location 12# - CSS file location 13# - Import alias (@/components)
Add Your First Component
bash1# Add individual components 2npx shadcn-ui@latest add button 3npx shadcn-ui@latest add card 4npx shadcn-ui@latest add dialog 5 6# Add multiple components at once 7npx shadcn-ui@latest add button card dialog form input
Basic Usage
tsx1import { Button } from "@/components/ui/button" 2import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card" 3 4export default function Example() { 5 return ( 6 <Card> 7 <CardHeader> 8 <CardTitle>Welcome</CardTitle> 9 </CardHeader> 10 <CardContent> 11 <Button>Click me</Button> 12 </CardContent> 13 </Card> 14 ) 15}
Architecture
Copy-Paste Philosophy
Key Difference from Traditional Libraries:
- Traditional:
npm install component-library→ locked to package versions - shadcn/ui: Components copied to
components/ui/→ you own the code
Benefits:
- Full customization control
- No breaking changes from package updates
- Easy to modify for specific needs
- Transparent implementation
- Tree-shakeable by default
Component Structure
src/
├── components/
│ └── ui/
│ ├── button.tsx # Component implementation
│ ├── card.tsx # Owns its code
│ ├── dialog.tsx # Modifiable
│ └── ...
├── lib/
│ └── utils.ts # cn() helper for class merging
└── app/
└── globals.css # Tailwind directives + CSS variables
Technology Stack
Core Dependencies:
- Radix UI: Accessible component primitives (headless UI)
- Tailwind CSS: Utility-first styling
- TypeScript: Type safety
- class-variance-authority (CVA): Variant management
- clsx: Class name concatenation
- tailwind-merge: Conflict-free class merging
Radix UI Integration:
tsx1// shadcn/ui components wrap Radix primitives 2import * as DialogPrimitive from "@radix-ui/react-dialog" 3 4// Add styling and variants 5const Dialog = DialogPrimitive.Root 6const DialogTrigger = DialogPrimitive.Trigger 7const DialogContent = React.forwardRef<...>( 8 ({ className, children, ...props }, ref) => ( 9 <DialogPrimitive.Content 10 ref={ref} 11 className={cn("fixed ...", className)} 12 {...props} 13 /> 14 ) 15)
Configuration
components.json
json1{ 2 "$schema": "https://ui.shadcn.com/schema.json", 3 "style": "default", 4 "rsc": true, 5 "tsx": true, 6 "tailwind": { 7 "config": "tailwind.config.ts", 8 "css": "app/globals.css", 9 "baseColor": "slate", 10 "cssVariables": true, 11 "prefix": "" 12 }, 13 "aliases": { 14 "components": "@/components", 15 "utils": "@/lib/utils", 16 "ui": "@/components/ui", 17 "lib": "@/lib", 18 "hooks": "@/hooks" 19 } 20}
Key Options:
style: "default" or "new-york" (design variants)rsc: React Server Components supportcssVariables: Use CSS variables for themingprefix: Tailwind class prefix (optional)
Tailwind Configuration
ts1// tailwind.config.ts 2import type { Config } from "tailwindcss" 3 4const config = { 5 darkMode: ["class"], 6 content: [ 7 './pages/**/*.{ts,tsx}', 8 './components/**/*.{ts,tsx}', 9 './app/**/*.{ts,tsx}', 10 './src/**/*.{ts,tsx}', 11 ], 12 prefix: "", 13 theme: { 14 container: { 15 center: true, 16 padding: "2rem", 17 screens: { 18 "2xl": "1400px", 19 }, 20 }, 21 extend: { 22 colors: { 23 border: "hsl(var(--border))", 24 input: "hsl(var(--input))", 25 ring: "hsl(var(--ring))", 26 background: "hsl(var(--background))", 27 foreground: "hsl(var(--foreground))", 28 primary: { 29 DEFAULT: "hsl(var(--primary))", 30 foreground: "hsl(var(--primary-foreground))", 31 }, 32 secondary: { 33 DEFAULT: "hsl(var(--secondary))", 34 foreground: "hsl(var(--secondary-foreground))", 35 }, 36 destructive: { 37 DEFAULT: "hsl(var(--destructive))", 38 foreground: "hsl(var(--destructive-foreground))", 39 }, 40 muted: { 41 DEFAULT: "hsl(var(--muted))", 42 foreground: "hsl(var(--muted-foreground))", 43 }, 44 accent: { 45 DEFAULT: "hsl(var(--accent))", 46 foreground: "hsl(var(--accent-foreground))", 47 }, 48 popover: { 49 DEFAULT: "hsl(var(--popover))", 50 foreground: "hsl(var(--popover-foreground))", 51 }, 52 card: { 53 DEFAULT: "hsl(var(--card))", 54 foreground: "hsl(var(--card-foreground))", 55 }, 56 }, 57 borderRadius: { 58 lg: "var(--radius)", 59 md: "calc(var(--radius) - 2px)", 60 sm: "calc(var(--radius) - 4px)", 61 }, 62 keyframes: { 63 "accordion-down": { 64 from: { height: "0" }, 65 to: { height: "var(--radix-accordion-content-height)" }, 66 }, 67 "accordion-up": { 68 from: { height: "var(--radix-accordion-content-height)" }, 69 to: { height: "0" }, 70 }, 71 }, 72 animation: { 73 "accordion-down": "accordion-down 0.2s ease-out", 74 "accordion-up": "accordion-up 0.2s ease-out", 75 }, 76 }, 77 }, 78 plugins: [require("tailwindcss-animate")], 79} satisfies Config 80 81export default config
CSS Variables (globals.css)
css1@tailwind base; 2@tailwind components; 3@tailwind utilities; 4 5@layer base { 6 :root { 7 --background: 0 0% 100%; 8 --foreground: 222.2 84% 4.9%; 9 --card: 0 0% 100%; 10 --card-foreground: 222.2 84% 4.9%; 11 --popover: 0 0% 100%; 12 --popover-foreground: 222.2 84% 4.9%; 13 --primary: 222.2 47.4% 11.2%; 14 --primary-foreground: 210 40% 98%; 15 --secondary: 210 40% 96.1%; 16 --secondary-foreground: 222.2 47.4% 11.2%; 17 --muted: 210 40% 96.1%; 18 --muted-foreground: 215.4 16.3% 46.9%; 19 --accent: 210 40% 96.1%; 20 --accent-foreground: 222.2 47.4% 11.2%; 21 --destructive: 0 84.2% 60.2%; 22 --destructive-foreground: 210 40% 98%; 23 --border: 214.3 31.8% 91.4%; 24 --input: 214.3 31.8% 91.4%; 25 --ring: 222.2 84% 4.9%; 26 --radius: 0.5rem; 27 } 28 29 .dark { 30 --background: 222.2 84% 4.9%; 31 --foreground: 210 40% 98%; 32 --card: 222.2 84% 4.9%; 33 --card-foreground: 210 40% 98%; 34 --popover: 222.2 84% 4.9%; 35 --popover-foreground: 210 40% 98%; 36 --primary: 210 40% 98%; 37 --primary-foreground: 222.2 47.4% 11.2%; 38 --secondary: 217.2 32.6% 17.5%; 39 --secondary-foreground: 210 40% 98%; 40 --muted: 217.2 32.6% 17.5%; 41 --muted-foreground: 215 20.2% 65.1%; 42 --accent: 217.2 32.6% 17.5%; 43 --accent-foreground: 210 40% 98%; 44 --destructive: 0 62.8% 30.6%; 45 --destructive-foreground: 210 40% 98%; 46 --border: 217.2 32.6% 17.5%; 47 --input: 217.2 32.6% 17.5%; 48 --ring: 212.7 26.8% 83.9%; 49 } 50} 51 52@layer base { 53 * { 54 @apply border-border; 55 } 56 body { 57 @apply bg-background text-foreground; 58 } 59}
Component Catalog
Button
tsx1import { Button } from "@/components/ui/button" 2 3// Variants 4<Button variant="default">Default</Button> 5<Button variant="destructive">Destructive</Button> 6<Button variant="outline">Outline</Button> 7<Button variant="secondary">Secondary</Button> 8<Button variant="ghost">Ghost</Button> 9<Button variant="link">Link</Button> 10 11// Sizes 12<Button size="default">Default</Button> 13<Button size="sm">Small</Button> 14<Button size="lg">Large</Button> 15<Button size="icon"><Icon /></Button> 16 17// States 18<Button disabled>Disabled</Button> 19<Button asChild> 20 <Link href="/about">As Link</Link> 21</Button>
Implementation Pattern (CVA):
tsx1import { cva, type VariantProps } from "class-variance-authority" 2 3const buttonVariants = cva( 4 "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50", 5 { 6 variants: { 7 variant: { 8 default: "bg-primary text-primary-foreground hover:bg-primary/90", 9 destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", 10 outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 11 secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", 12 ghost: "hover:bg-accent hover:text-accent-foreground", 13 link: "text-primary underline-offset-4 hover:underline", 14 }, 15 size: { 16 default: "h-10 px-4 py-2", 17 sm: "h-9 rounded-md px-3", 18 lg: "h-11 rounded-md px-8", 19 icon: "h-10 w-10", 20 }, 21 }, 22 defaultVariants: { 23 variant: "default", 24 size: "default", 25 }, 26 } 27)
Card
tsx1import { 2 Card, 3 CardHeader, 4 CardFooter, 5 CardTitle, 6 CardDescription, 7 CardContent, 8} from "@/components/ui/card" 9 10<Card> 11 <CardHeader> 12 <CardTitle>Card Title</CardTitle> 13 <CardDescription>Card description goes here</CardDescription> 14 </CardHeader> 15 <CardContent> 16 <p>Card content</p> 17 </CardContent> 18 <CardFooter> 19 <Button>Action</Button> 20 </CardFooter> 21</Card>
Dialog (Modal)
tsx1import { 2 Dialog, 3 DialogContent, 4 DialogDescription, 5 DialogHeader, 6 DialogTitle, 7 DialogTrigger, 8 DialogFooter, 9} from "@/components/ui/dialog" 10 11<Dialog> 12 <DialogTrigger asChild> 13 <Button>Open Dialog</Button> 14 </DialogTrigger> 15 <DialogContent> 16 <DialogHeader> 17 <DialogTitle>Are you sure?</DialogTitle> 18 <DialogDescription> 19 This action cannot be undone. 20 </DialogDescription> 21 </DialogHeader> 22 <DialogFooter> 23 <Button variant="outline">Cancel</Button> 24 <Button>Confirm</Button> 25 </DialogFooter> 26 </DialogContent> 27</Dialog>
Form (with react-hook-form + zod)
tsx1import { zodResolver } from "@hookform/resolvers/zod" 2import { useForm } from "react-hook-form" 3import * as z from "zod" 4import { 5 Form, 6 FormControl, 7 FormDescription, 8 FormField, 9 FormItem, 10 FormLabel, 11 FormMessage, 12} from "@/components/ui/form" 13import { Input } from "@/components/ui/input" 14import { Button } from "@/components/ui/button" 15 16const formSchema = z.object({ 17 username: z.string().min(2, { 18 message: "Username must be at least 2 characters.", 19 }), 20 email: z.string().email({ 21 message: "Please enter a valid email address.", 22 }), 23}) 24 25function ProfileForm() { 26 const form = useForm<z.infer<typeof formSchema>>({ 27 resolver: zodResolver(formSchema), 28 defaultValues: { 29 username: "", 30 email: "", 31 }, 32 }) 33 34 function onSubmit(values: z.infer<typeof formSchema>) { 35 console.log(values) 36 } 37 38 return ( 39 <Form {...form}> 40 <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> 41 <FormField 42 control={form.control} 43 name="username" 44 render={({ field }) => ( 45 <FormItem> 46 <FormLabel>Username</FormLabel> 47 <FormControl> 48 <Input placeholder="shadcn" {...field} /> 49 </FormControl> 50 <FormDescription> 51 This is your public display name. 52 </FormDescription> 53 <FormMessage /> 54 </FormItem> 55 )} 56 /> 57 <FormField 58 control={form.control} 59 name="email" 60 render={({ field }) => ( 61 <FormItem> 62 <FormLabel>Email</FormLabel> 63 <FormControl> 64 <Input type="email" placeholder="user@example.com" {...field} /> 65 </FormControl> 66 <FormMessage /> 67 </FormItem> 68 )} 69 /> 70 <Button type="submit">Submit</Button> 71 </form> 72 </Form> 73 ) 74}
Table
tsx1import { 2 Table, 3 TableBody, 4 TableCaption, 5 TableCell, 6 TableHead, 7 TableHeader, 8 TableRow, 9} from "@/components/ui/table" 10 11<Table> 12 <TableCaption>A list of your recent invoices.</TableCaption> 13 <TableHeader> 14 <TableRow> 15 <TableHead>Invoice</TableHead> 16 <TableHead>Status</TableHead> 17 <TableHead>Method</TableHead> 18 <TableHead className="text-right">Amount</TableHead> 19 </TableRow> 20 </TableHeader> 21 <TableBody> 22 <TableRow> 23 <TableCell>INV001</TableCell> 24 <TableCell>Paid</TableCell> 25 <TableCell>Credit Card</TableCell> 26 <TableCell className="text-right">$250.00</TableCell> 27 </TableRow> 28 </TableBody> 29</Table>
Additional Components
Available via CLI:
accordion- Collapsible content sectionsalert- Contextual feedback messagesalert-dialog- Interrupting modal dialogsavatar- User profile imagesbadge- Status indicatorscalendar- Date pickercheckbox- Binary inputcommand- Command palette (⌘K menu)context-menu- Right-click menusdropdown-menu- Dropdown menushover-card- Hover tooltipsinput- Text inputlabel- Form labelsmenubar- Application menu barnavigation-menu- Site navigationpopover- Floating panelsprogress- Progress indicatorsradio-group- Radio button groupsscroll-area- Custom scrollbarsselect- Dropdown selectsseparator- Visual dividerssheet- Side panelsskeleton- Loading placeholdersslider- Range inputswitch- Toggle switchtabs- Tab navigationtextarea- Multi-line inputtoast- Notification toaststoggle- Toggle buttontooltip- Hover tooltips
Theming
Color Customization
Change base color scheme:
bash1# Regenerate components with new base color 2npx shadcn-ui@latest init 3 4# Choose new base: Slate, Gray, Zinc, Neutral, Stone
Manual color override (globals.css):
css1:root { 2 --primary: 210 100% 50%; /* HSL: Blue */ 3 --primary-foreground: 0 0% 100%; 4} 5 6.dark { 7 --primary: 210 100% 60%; /* Lighter blue for dark mode */ 8}
Custom Variants
tsx1// Extend button variants 2const buttonVariants = cva( 3 "...", 4 { 5 variants: { 6 variant: { 7 // ...existing variants 8 gradient: "bg-gradient-to-r from-purple-500 to-pink-500 text-white", 9 }, 10 }, 11 } 12) 13 14// Usage 15<Button variant="gradient">Gradient Button</Button>
Theme Switching
tsx1// Using next-themes 2import { ThemeProvider } from "next-themes" 3 4// app/layout.tsx 5export default function RootLayout({ children }) { 6 return ( 7 <html lang="en" suppressHydrationWarning> 8 <body> 9 <ThemeProvider 10 attribute="class" 11 defaultTheme="system" 12 enableSystem 13 disableTransitionOnChange 14 > 15 {children} 16 </ThemeProvider> 17 </body> 18 </html> 19 ) 20} 21 22// Theme toggle component 23import { Moon, Sun } from "lucide-react" 24import { useTheme } from "next-themes" 25import { Button } from "@/components/ui/button" 26 27export function ThemeToggle() { 28 const { setTheme, theme } = useTheme() 29 30 return ( 31 <Button 32 variant="ghost" 33 size="icon" 34 onClick={() => setTheme(theme === "light" ? "dark" : "light")} 35 > 36 <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> 37 <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> 38 <span className="sr-only">Toggle theme</span> 39 </Button> 40 ) 41}
Dark Mode
Setup with Next.js
bash1npm install next-themes
tsx1// app/providers.tsx 2"use client" 3 4import { ThemeProvider } from "next-themes" 5 6export function Providers({ children }: { children: React.ReactNode }) { 7 return ( 8 <ThemeProvider attribute="class" defaultTheme="system" enableSystem> 9 {children} 10 </ThemeProvider> 11 ) 12} 13 14// app/layout.tsx 15import { Providers } from "./providers" 16 17export default function RootLayout({ children }) { 18 return ( 19 <html lang="en" suppressHydrationWarning> 20 <body> 21 <Providers>{children}</Providers> 22 </body> 23 </html> 24 ) 25}
Dark Mode Utilities
tsx1// Force dark mode for specific section 2<div className="dark"> 3 <Card>Always dark, regardless of theme</Card> 4</div> 5 6// Conditional styling 7<div className="bg-white dark:bg-slate-950"> 8 <p className="text-slate-900 dark:text-slate-50"> 9 Adapts to theme 10 </p> 11</div>
Next.js Integration
App Router Setup
bash1# Create Next.js app with TypeScript and Tailwind 2npx create-next-app@latest my-app --typescript --tailwind --app 3 4# Initialize shadcn/ui 5cd my-app 6npx shadcn-ui@latest init 7 8# Add components 9npx shadcn-ui@latest add button card form
Server Components
tsx1// app/page.tsx (Server Component by default) 2import { Button } from "@/components/ui/button" 3 4export default function HomePage() { 5 return ( 6 <main> 7 <h1>Welcome</h1> 8 {/* Static components work in Server Components */} 9 <Button asChild> 10 <a href="/about">Learn More</a> 11 </Button> 12 </main> 13 ) 14}
Client Components
tsx1// app/interactive.tsx 2"use client" 3 4import { useState } from "react" 5import { Button } from "@/components/ui/button" 6import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog" 7 8export function InteractiveSection() { 9 const [open, setOpen] = useState(false) 10 11 return ( 12 <Dialog open={open} onOpenChange={setOpen}> 13 <DialogTrigger asChild> 14 <Button>Open Dialog</Button> 15 </DialogTrigger> 16 <DialogContent> 17 <p>Client-side interactivity</p> 18 </DialogContent> 19 </Dialog> 20 ) 21}
Route Handlers
tsx1// app/api/submit/route.ts 2import { NextResponse } from "next/server" 3import { z } from "zod" 4 5const formSchema = z.object({ 6 email: z.string().email(), 7 message: z.string().min(10), 8}) 9 10export async function POST(request: Request) { 11 try { 12 const body = await request.json() 13 const validatedData = formSchema.parse(body) 14 15 // Process form data 16 return NextResponse.json({ success: true }) 17 } catch (error) { 18 if (error instanceof z.ZodError) { 19 return NextResponse.json({ errors: error.errors }, { status: 400 }) 20 } 21 return NextResponse.json({ error: "Internal error" }, { status: 500 }) 22 } 23}
Accessibility
ARIA Support
All shadcn/ui components include proper ARIA attributes via Radix UI:
tsx1// Dialog automatically includes: 2// - role="dialog" 3// - aria-describedby 4// - aria-labelledby 5// - Focus trap 6// - Escape key handler 7<Dialog> 8 <DialogContent> 9 {/* Automatically accessible */} 10 </DialogContent> 11</Dialog> 12 13// Button includes: 14// - role="button" 15// - tabindex="0" 16// - Keyboard activation (Space/Enter) 17<Button>Accessible by default</Button>
Keyboard Navigation
Built-in keyboard support:
Tab/Shift+Tab- Navigate between interactive elementsEnter/Space- Activate buttonsEscape- Close dialogs, dropdowns, popoversArrow keys- Navigate menus, select options, radio groupsHome/End- Jump to first/last in lists
Example: Command Palette:
tsx1import { 2 Command, 3 CommandDialog, 4 CommandInput, 5 CommandList, 6 CommandEmpty, 7 CommandGroup, 8 CommandItem, 9} from "@/components/ui/command" 10 11// ⌘K to open 12<CommandDialog open={open} onOpenChange={setOpen}> 13 <CommandInput placeholder="Type a command..." /> 14 <CommandList> 15 <CommandEmpty>No results found.</CommandEmpty> 16 <CommandGroup heading="Suggestions"> 17 <CommandItem>Calendar</CommandItem> 18 <CommandItem>Search Emoji</CommandItem> 19 <CommandItem>Calculator</CommandItem> 20 </CommandGroup> 21 </CommandList> 22</CommandDialog>
Screen Reader Support
tsx1// Visually hidden but accessible to screen readers 2<span className="sr-only">Close dialog</span> 3 4// Skip navigation links 5<a href="#main-content" className="sr-only focus:not-sr-only"> 6 Skip to main content 7</a> 8 9// Descriptive labels 10<FormLabel htmlFor="email">Email address</FormLabel> 11<Input 12 id="email" 13 type="email" 14 aria-describedby="email-description" 15 aria-invalid={!!errors.email} 16/> 17<FormDescription id="email-description"> 18 We'll never share your email. 19</FormDescription>
Focus Management
tsx1// Focus trap in Dialog (automatic) 2<Dialog> 3 <DialogContent> 4 {/* Focus stays within dialog until closed */} 5 </DialogContent> 6</Dialog> 7 8// Custom focus management 9import { useRef, useEffect } from "react" 10 11function CustomComponent() { 12 const inputRef = useRef<HTMLInputElement>(null) 13 14 useEffect(() => { 15 inputRef.current?.focus() 16 }, []) 17 18 return <Input ref={inputRef} /> 19}
Composition Patterns
Compound Components
tsx1// Card composition 2<Card> 3 <CardHeader> 4 <CardTitle>Title</CardTitle> 5 <CardDescription>Description</CardDescription> 6 </CardHeader> 7 <CardContent>Content</CardContent> 8 <CardFooter>Footer</CardFooter> 9</Card> 10 11// Form composition 12<Form {...form}> 13 <FormField 14 control={form.control} 15 name="field" 16 render={({ field }) => ( 17 <FormItem> 18 <FormLabel>Label</FormLabel> 19 <FormControl> 20 <Input {...field} /> 21 </FormControl> 22 <FormDescription>Help text</FormDescription> 23 <FormMessage /> 24 </FormItem> 25 )} 26 /> 27</Form>
Polymorphic Components (asChild)
tsx1// Render Button as Link 2import { Button } from "@/components/ui/button" 3import Link from "next/link" 4 5<Button asChild> 6 <Link href="/dashboard">Go to Dashboard</Link> 7</Button> 8 9// Render as custom component 10<Button asChild> 11 <motion.button 12 whileHover={{ scale: 1.05 }} 13 whileTap={{ scale: 0.95 }} 14 > 15 Animated Button 16 </motion.button> 17</Button>
How it works (Radix Slot):
tsx1import { Slot } from "@radix-ui/react-slot" 2 3interface ButtonProps { 4 asChild?: boolean 5} 6 7const Button = ({ asChild, ...props }: ButtonProps) => { 8 const Comp = asChild ? Slot : "button" 9 return <Comp {...props} /> 10}
Custom Compositions
tsx1// Create custom card variant 2export function PricingCard({ 3 title, 4 price, 5 features, 6 highlighted 7}: PricingCardProps) { 8 return ( 9 <Card className={cn(highlighted && "border-primary")}> 10 <CardHeader> 11 <CardTitle>{title}</CardTitle> 12 <CardDescription className="text-3xl font-bold"> 13 ${price}/mo 14 </CardDescription> 15 </CardHeader> 16 <CardContent> 17 <ul className="space-y-2"> 18 {features.map((feature) => ( 19 <li key={feature} className="flex items-center"> 20 <Check className="mr-2 h-4 w-4 text-primary" /> 21 {feature} 22 </li> 23 ))} 24 </ul> 25 </CardContent> 26 <CardFooter> 27 <Button className="w-full" variant={highlighted ? "default" : "outline"}> 28 Get Started 29 </Button> 30 </CardFooter> 31 </Card> 32 ) 33}
CLI Commands
Initialize
bash1# Interactive init 2npx shadcn-ui@latest init 3 4# Non-interactive with defaults 5npx shadcn-ui@latest init -y 6 7# Specify options 8npx shadcn-ui@latest init --typescript --tailwind
Add Components
bash1# Single component 2npx shadcn-ui@latest add button 3 4# Multiple components 5npx shadcn-ui@latest add button card dialog form 6 7# All components (not recommended - adds everything) 8npx shadcn-ui@latest add --all 9 10# Specific version 11npx shadcn-ui@latest add button@1.0.0 12 13# Overwrite existing 14npx shadcn-ui@latest add button --overwrite 15 16# Different path 17npx shadcn-ui@latest add button --path src/components/ui
Diff Components
bash1# Check for component updates 2npx shadcn-ui@latest diff 3 4# Diff specific component 5npx shadcn-ui@latest diff button 6 7# Show what would change 8npx shadcn-ui@latest diff --check
Update Components
bash1# Update all components 2npx shadcn-ui@latest update 3 4# Update specific components 5npx shadcn-ui@latest update button card 6 7# Preview changes before applying 8npx shadcn-ui@latest update --dry-run
Advanced Patterns
Custom Hooks
tsx1// useToast hook (built-in with toast component) 2import { useToast } from "@/components/ui/use-toast" 3 4function MyComponent() { 5 const { toast } = useToast() 6 7 return ( 8 <Button 9 onClick={() => { 10 toast({ 11 title: "Scheduled: Catch up", 12 description: "Friday, February 10, 2023 at 5:57 PM", 13 }) 14 }} 15 > 16 Show Toast 17 </Button> 18 ) 19} 20 21// Custom form hook 22import { useForm } from "react-hook-form" 23import { zodResolver } from "@hookform/resolvers/zod" 24 25function useFormWithToast<T extends z.ZodType>(schema: T) { 26 const { toast } = useToast() 27 const form = useForm({ 28 resolver: zodResolver(schema), 29 }) 30 31 const handleSubmit = form.handleSubmit(async (data) => { 32 try { 33 // Submit logic 34 toast({ title: "Success!" }) 35 } catch (error) { 36 toast({ title: "Error", variant: "destructive" }) 37 } 38 }) 39 40 return { form, handleSubmit } 41}
Responsive Design
tsx1// Mobile-first responsive components 2<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> 3 <Card>Mobile: 1 col, Tablet: 2 col, Desktop: 3 col</Card> 4</div> 5 6// Responsive dialog (sheet on mobile, dialog on desktop) 7import { useMediaQuery } from "@/hooks/use-media-query" 8import { Dialog, DialogContent } from "@/components/ui/dialog" 9import { Sheet, SheetContent } from "@/components/ui/sheet" 10 11function ResponsiveModal({ children, ...props }) { 12 const isDesktop = useMediaQuery("(min-width: 768px)") 13 14 if (isDesktop) { 15 return ( 16 <Dialog {...props}> 17 <DialogContent>{children}</DialogContent> 18 </Dialog> 19 ) 20 } 21 22 return ( 23 <Sheet {...props}> 24 <SheetContent>{children}</SheetContent> 25 </Sheet> 26 ) 27}
Animation Variants
tsx1// Using Framer Motion with shadcn/ui 2import { motion } from "framer-motion" 3import { Card } from "@/components/ui/card" 4 5const MotionCard = motion(Card) 6 7<MotionCard 8 initial={{ opacity: 0, y: 20 }} 9 animate={{ opacity: 1, y: 0 }} 10 transition={{ duration: 0.3 }} 11> 12 Animated Card 13</MotionCard> 14 15// Staggered list animation 16const container = { 17 hidden: { opacity: 0 }, 18 show: { 19 opacity: 1, 20 transition: { 21 staggerChildren: 0.1 22 } 23 } 24} 25 26const item = { 27 hidden: { opacity: 0, y: 20 }, 28 show: { opacity: 1, y: 0 } 29} 30 31<motion.ul variants={container} initial="hidden" animate="show"> 32 {items.map((item) => ( 33 <motion.li key={item.id} variants={item}> 34 <Card>{item.content}</Card> 35 </motion.li> 36 ))} 37</motion.ul>
Best Practices
Code Organization:
- Keep shadcn/ui components in
components/ui/(don't mix with app components) - Create custom compositions in
components/(outside ui/) - Use
lib/utils.tsfor shared utilities
Customization:
- Modify components directly in your project (you own the code)
- Use CSS variables for theme-wide changes
- Extend variants with CVA for new styles
- Don't edit
components.jsonmanually (use CLI)
Performance:
- Tree-shaking automatic (only imports what you use)
- Use
asChildto avoid unnecessary wrapper elements - Lazy load heavy components (Calendar, Command)
- Prefer Server Components when possible (Next.js)
Accessibility:
- Don't remove ARIA attributes from components
- Test keyboard navigation for custom compositions
- Maintain focus management in dialogs/modals
- Use semantic HTML with
asChildwhen applicable
TypeScript:
- Leverage exported types (ButtonProps, CardProps, etc.)
- Use VariantProps for variant type safety
- Add strict null checks for form validation
Troubleshooting
Import errors:
bash1# Check path aliases in tsconfig.json 2{ 3 "compilerOptions": { 4 "baseUrl": ".", 5 "paths": { 6 "@/*": ["./src/*"] 7 } 8 } 9}
Tailwind classes not applying:
ts1// Ensure content paths include your components 2// tailwind.config.ts 3content: [ 4 './src/components/**/*.{ts,tsx}', // Add this 5 './src/app/**/*.{ts,tsx}', 6]
Dark mode not working:
tsx1// Add suppressHydrationWarning to <html> 2<html lang="en" suppressHydrationWarning>
Form validation not triggering:
tsx1// Ensure FormMessage is included in FormField 2<FormField> 3 <FormItem> 4 <FormControl>...</FormControl> 5 <FormMessage /> {/* Required for errors */} 6 </FormItem> 7</FormField>
Resources
- Official Docs: https://ui.shadcn.com
- Component Source: https://github.com/shadcn-ui/ui
- Radix UI Docs: https://www.radix-ui.com
- Tailwind CSS: https://tailwindcss.com
- CVA Docs: https://cva.style/docs
- Examples: https://ui.shadcn.com/examples
- Community: https://discord.gg/shadcn-ui