skills/shadcn-ui/SKILL.md
Use when building React UIs with component libraries, implementing forms, dialogs, navigation, or data display. Use when user mentions shadcn, Radix UI, Base UI, or asks about accessible React components. Proactively suggest when building UI that would benefit from pre-built accessible components with Tailwind CSS styling.
npx skillsauth add akornmeier/claude-config shadcn-uiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
ShadCN/UI is a collection of beautifully-designed, accessible components built with TypeScript, Tailwind CSS, and headless UI primitives (Base UI or Radix UI). Unlike traditional component libraries, ShadCN uses a copy-paste model - components are copied into YOUR project, giving you full ownership and customization control.
Core Principle: You own the code. Components live in your project (typically src/components/ui/), not in node_modules. This fundamentally changes how you think about customization - edit the source directly.
During project creation (shadcn create), choose your base primitives:
| Base Library | Choose When | |--------------|-------------| | Base UI | Prefer MUI ecosystem, need unstyled primitives with strong React patterns | | Radix UI | Want extensive primitive catalog, strong accessibility defaults |
Example preset with Base UI:
pnpm dlx shadcn@latest create --preset "https://ui.shadcn.com/init?base=base&style=nova&baseColor=stone" --template vite
Need user input?
→ Form & Input category (Input, Select, Checkbox, Form)
Need to show/hide content?
→ Triggered by click/hover? → Overlays (Dialog, Popover, Tooltip, Sheet)
→ User-controlled toggle? → Layout (Accordion, Tabs, Collapsible)
Need to display data?
→ Single item → Card
→ Multiple items → Table or Data Table
Need feedback?
→ Temporary → Toast (Sonner)
→ Persistent → Alert
Refer to llms.txt in this skill directory for the full component index with documentation links.
ShadCN copies components into YOUR project. This means:
ShadCN components are composable primitives, not monolithic widgets:
// Anti-pattern: Expecting a single "FormInput" component
<FormInput label="Email" error={errors.email} />
// ShadCN pattern: Compose primitives
<FormField>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormField>
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
const schema = z.object({
email: z.string().email("Invalid email address"),
name: z.string().min(2, "Name must be at least 2 characters"),
})
type FormData = z.infer<typeof schema>
function ContactForm() {
const form = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { email: "", name: "" },
})
const onSubmit = (data: FormData) => {
console.log(data)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="John Doe" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="[email protected]" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { useState } from "react"
function DialogWithForm() {
const [open, setOpen] = useState(false)
const handleSuccess = () => {
setOpen(false) // Close dialog on successful submit
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Open Form</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Contact Us</DialogTitle>
<DialogDescription>
Fill out the form below and we'll get back to you.
</DialogDescription>
</DialogHeader>
<ContactForm onSuccess={handleSuccess} />
</DialogContent>
</Dialog>
)
}
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
function ToastExample() {
return (
<div className="space-x-2">
<Button onClick={() => toast("Event created")}>
Default
</Button>
<Button onClick={() => toast.success("Successfully saved!")}>
Success
</Button>
<Button onClick={() => toast.error("Something went wrong")}>
Error
</Button>
<Button
onClick={() =>
toast.promise(saveData(), {
loading: "Saving...",
success: "Data saved!",
error: "Could not save",
})
}
>
With Promise
</Button>
</div>
)
}
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
interface User {
id: string
name: string
email: string
role: string
}
function UsersTable({ users }: { users: User[] }) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.role}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}
For advanced sorting, filtering, and pagination, use @tanstack/react-table with the Data Table component. See: https://ui.shadcn.com/docs/components/data-table
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import { useEffect, useState } from "react"
function CommandPalette() {
const [open, setOpen] = useState(false)
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
setOpen((open) => !open)
}
}
document.addEventListener("keydown", down)
return () => document.removeEventListener("keydown", down)
}, [])
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Actions">
<CommandItem>New File</CommandItem>
<CommandItem>New Folder</CommandItem>
<CommandItem>Settings</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
)
}
import { Moon, Sun } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useTheme } from "@/components/theme-provider"
function ModeToggle() {
const { theme, setTheme } = useTheme()
return (
<Button
variant="outline"
size="icon"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
>
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}
npx shadcn@latest mcp init --client claude
This creates .mcp.json:
{
"mcpServers": {
"shadcn": {
"command": "npx",
"args": ["shadcn@latest", "mcp"]
}
}
}
npx shadcn@latest mcp init --client cursor # Cursor
npx shadcn@latest mcp init --client vscode # VS Code
npx shadcn@latest mcp init --client codex # Codex
Once configured, MCP tools allow:
The MCP server understands requests like:
Configure private/custom registries in components.json:
{
"registries": {
"@company": "https://registry.company.com/{name}.json"
}
}
| Need | Use | |------|-----| | Browse/install components | MCP tools | | Understand component API | WebFetch to docs URL from llms.txt | | Check what's available | MCP browse or llms.txt index |
// Wrong - ShadCN components live in YOUR project
import { Button } from "@shadcn/ui"
// Correct - import from your components directory
import { Button } from "@/components/ui/button"
// Wrong - expecting MUI-style prop customization
<Button colorScheme="purple" size="xl" leftIcon={<Icon />}>
// Correct - use Tailwind classes + composition
<Button className="bg-purple-600 text-lg">
<Icon className="mr-2 h-4 w-4" />
Click me
</Button>
Components may require peer dependencies. Common ones:
react-hook-form, @hookform/resolvers, zod@tanstack/react-tabledate-fns, react-day-pickerrechartssonner# Must run init before adding components
npx shadcn@latest init
npx shadcn@latest add button dialog card
| Issue | Solution |
|-------|----------|
| Components not styled | Ensure Tailwind config includes ./src/components/**/*.{ts,tsx} in content paths |
| TypeScript errors | Run npx shadcn@latest init to generate proper tsconfig paths |
| Dark mode not working | Check darkMode: "class" in tailwind.config + wrap app in ThemeProvider |
| Path alias issues (@/) | Verify baseUrl and paths in tsconfig.json match components.json |
| Component not found | Run npx shadcn@latest add <component-name> to install it |
# Create new project with preset
pnpm dlx shadcn@latest create --template vite
# Or add to existing project
npx shadcn@latest init
npx shadcn@latest add button # Single component
npx shadcn@latest add button card # Multiple components
npx shadcn@latest add --all # All components
src/
components/
ui/ # ShadCN components live here
button.tsx
card.tsx
dialog.tsx
...
lib/
utils.ts # cn() utility for className merging
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// Usage - merge classes safely
<Button className={cn("mt-4", isActive && "bg-primary")}>
llms.txt in this skill directorytools
Use when translating UX specifications into build-order prompts for UI generation tools. Triggers when user has a UX spec, PRD, or detailed feature doc and needs sequential, self-contained prompts for tools like v0, Bolt, or Claude frontend-design.
development
Guide for implementing Turborepo - a high-performance build system for JavaScript and TypeScript monorepos. Use when setting up monorepos, optimizing build performance, implementing task pipelines, configuring caching strategies, or orchestrating tasks across multiple packages.
tools
Replace with description of the skill and when Claude should use it.
tools
Guide for implementing Tailwind CSS - a utility-first CSS framework for rapid UI development. Use when styling applications with responsive design, dark mode, custom themes, or building design systems with Tailwind's utility classes.