---
name: shadcn-ui
description: shadcn/ui component library patterns, best practices, and MCP integration. Use when building UI with shadcn/ui components, customizing components, or using the shadcn MCP server. Triggers on tasks involving component installation, UI composition, theming, or when explicitly asked about shadcn patterns.
license: MIT
metadata:
author: beth-team
version: "1.0.0"
registry: https://ui.shadcn.com
---
# shadcn/ui Component Skill
This skill covers shadcn/ui component patterns, installation, customization, and MCP server integration for AI-assisted development.
## What is shadcn/ui?
shadcn/ui is **not a component library**—it's a code distribution system. You own the code. Components are copied into your project where you can customize them freely.
Key principles:
- **Open Code**: You get the actual component source code
- **Composition**: Components share a consistent, composable API
- **Beautiful Defaults**: Carefully designed defaults that work together
- **AI-Ready**: Open code for LLMs to read, understand, and modify
## MCP Server Integration
The shadcn MCP server enables AI assistants to interact directly with component registries.
### Configuration
**VS Code (GitHub Copilot):**
```bash
npx shadcn@latest mcp init --client vscode
```
Creates `.vscode/mcp.json`:
```json
{
"servers": {
"shadcn": {
"command": "npx",
"args": ["shadcn@latest", "mcp"]
}
}
}
```
**Other Clients:**
- Claude Code: `npx shadcn@latest mcp init --client claude`
- Cursor: `npx shadcn@latest mcp init --client cursor`
- Codex: Manual config in `~/.codex/config.toml`
### MCP Tools
| Tool | Purpose | Example Use |
|------|---------|-------------|
| `get_project_registries` | List configured registries | Verify setup |
| `search_items_in_registries` | Search by name/function | "Find data table component" |
| `list_items_in_registries` | Browse all components | Explore options |
| `get_item_details_from_registries` | Get full source code | Understand implementation |
| `get_item_examples_from_registries` | Get usage examples | Learn patterns |
| `add_items_to_project` | Install components | "Add button and card" |
### Example MCP Prompts
```
# Discovery
"Show me all available components in the shadcn registry"
"What form components are available?"
"Find me a date picker"
# Details
"Show me the code for the dialog component"
"Get examples of the data-table component"
"What are the variants for the button component?"
# Installation
"Add button, card, and dialog to this project"
"Install all the form components"
"Add the chart component"
```
## Project Setup
### Initialize a new project
```bash
npx shadcn@latest init
```
This creates:
- `components.json` - Configuration
- `lib/utils.ts` - Utility functions (cn helper)
- `tailwind.config.ts` - Tailwind configuration
- `globals.css` - CSS variables for theming
### Configuration (components.json)
```json
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui"
},
"registries": {
"@shadcn": "https://ui.shadcn.com/r/{name}.json"
}
}
```
### Add components
```bash
# Single component
npx shadcn@latest add button
# Multiple components
npx shadcn@latest add button card dialog
# All components
npx shadcn@latest add --all
```
## Component Patterns
### Basic Usage
```tsx
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export function Example() {
return (
<Card>
<CardHeader>
<CardTitle>Hello World</CardTitle>
</CardHeader>
<CardContent>
<Button>Click Me</Button>
</CardContent>
</Card>
);
}
```
### Composition Pattern
shadcn/ui components are composable by design:
```tsx
// Dialog with custom trigger
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open Settings</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Settings</DialogTitle>
<DialogDescription>Configure your preferences.</DialogDescription>
</DialogHeader>
{/* Content */}
<DialogFooter>
<Button type="submit">Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
```
### The cn() Utility
The `cn()` helper merges Tailwind classes intelligently:
```tsx
import { cn } from "@/lib/utils";
function MyComponent({ className, ...props }) {
return (
<div className={cn("base-styles", className)} {...props} />
);
}
```
Implementation (uses clsx + tailwind-merge):
```ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
### Component Variants with cva
Use `class-variance-authority` for component variants:
```tsx
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground",
secondary: "border-transparent bg-secondary text-secondary-foreground",
destructive: "border-transparent bg-destructive text-destructive-foreground",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
```
## Theming
### CSS Variables
shadcn/ui uses CSS variables for theming:
```css
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
/* ... more variables */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
/* ... dark mode overrides */
}
```
### Using Theme Colors
```tsx
// In Tailwind classes
<div className="bg-background text-foreground">
<button className="bg-primary text-primary-foreground">
Primary Button
</button>
</div>
// Accessing in JS
const styles = {
backgroundColor: 'hsl(var(--background))',
color: 'hsl(var(--foreground))',
};
```
### Dark Mode
Configure dark mode in `tailwind.config.ts`:
```ts
module.exports = {
darkMode: ["class"],
// ...
}
```
Toggle with a theme provider:
```tsx
'use client';
import { ThemeProvider } from "next-themes";
export function Providers({ children }) {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
);
}
```
## Form Patterns
### With React Hook Form + Zod
```tsx
'use client';
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
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 schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export function LoginForm() {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { email: "", password: "" },
});
async function onSubmit(values: z.infer<typeof schema>) {
// Handle submission
}
return (
<Form {...form}>
<form className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Login</Button>
</form>
</Form>
);
}
```
## Data Display
### DataTable Pattern
```tsx
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
interface Column<T> {
header: string;
accessor: keyof T | ((row: T) => React.ReactNode);
}
interface DataTableProps<T> {
columns: Column<T>[];
data: T[];
}
export function DataTable<T extends { id: string }>({
columns,
data
}: DataTableProps<T>) {
return (
<Table>
<TableHeader>
<TableRow>
{columns.map((col, i) => (
<TableHead key={i}>{col.header}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{data.map((row) => (
<TableRow key={row.id}>
{columns.map((col, i) => (
<TableCell key={i}>
{typeof col.accessor === 'function'
? col.accessor(row)
: String(row[col.accessor])}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
);
}
```
## Accessibility
shadcn/ui components are built on Radix UI primitives which handle:
- Keyboard navigation
- Focus management
- ARIA attributes
- Screen reader announcements
### Ensure Accessibility in Custom Code
```tsx
// Always provide accessible labels
<Button aria-label="Close dialog">
<X className="h-4 w-4" />
</Button>
// Use sr-only for screen reader text
<span className="sr-only">Loading</span>
// Handle focus management
<Dialog => {
if (!open) {
// Return focus to trigger
triggerRef.current?.focus();
}
}}>
```
## Best Practices
### DO
- ✅ Accept `className` prop for composition
- ✅ Use `cn()` to merge classes
- ✅ Forward refs when needed
- ✅ Keep components focused and composable
- ✅ Use TypeScript for props
- ✅ Add loading/disabled states
### DON'T
- ❌ Modify components in `ui/` directly for one-off changes
- ❌ Override styles with `!important`
- ❌ Create wrapper components when composition works
- ❌ Skip accessibility testing
- ❌ Use deprecated patterns
### When to Customize vs Compose
**Compose** (use as-is with props):
```tsx
<Button size="lg" variant="outline">Large Outline</Button>
```
**Extend** (create specialized version):
```tsx
// components/IconButton.tsx
import { Button, ButtonProps } from "@/components/ui/button";
import { cn } from "@/lib/utils";
interface IconButtonProps extends ButtonProps {
icon: React.ReactNode;
}
export function IconButton({ icon, className, ...props }: IconButtonProps) {
return (
<Button className={cn("p-2", className)} {...props}>
{icon}
</Button>
);
}
```
**Customize** (modify source when pattern doesn't exist):
- Fork `ui/button.tsx` only for project-wide changes
- Document the customization
## Common Components Reference
| Component | Use For |
|-----------|---------|
| `Button` | Actions, form submissions |
| `Card` | Content containers |
| `Dialog` | Modal interactions |
| `Sheet` | Side panels |
| `Form` | Form containers with validation |
| `Input`, `Textarea` | Text inputs |
| `Select` | Dropdowns |
| `Checkbox`, `Radio`, `Switch` | Toggles |
| `Table` | Data display |
| `Tabs` | Content organization |
| `Toast` | Notifications |
| `Tooltip` | Contextual info |
| `Command` | Command palettes |
| `DropdownMenu` | Action menus |
| `NavigationMenu` | Site navigation |
| `Avatar` | User images |
| `Badge` | Status indicators |
| `Skeleton` | Loading placeholders |
## Troubleshooting
### Component not found
```bash
# Check available components
npx shadcn@latest add --help
# Search via MCP
"List all components in the shadcn registry"
```
### Styling conflicts
- Ensure Tailwind is properly configured
- Check CSS variable definitions
- Use `cn()` for class merging
### Dark mode not working
- Verify `darkMode: ["class"]` in tailwind.config
- Check ThemeProvider is wrapping your app
- Ensure CSS variables have dark mode overrides
### TypeScript errors
- Run `npx shadcn@latest add [component]` to get latest types
- Check component is exported from `ui/` index
Shadcn UI
````skill
Shadcn UI by stephschofield · 0e9d415
npx skillmds@latest add stephschofield/shadcn-ui File contents
---name: shadcn-uidescription: ````skill---````skill---name: shadcn-uidescription: shadcn/ui component library patterns, best practices, and MCP integration. Use when building UI with shadcn/ui components, customizing components, or using the shadcn MCP server. Triggers on tasks involving component installation, UI composition, theming, or when explicitly asked about shadcn patterns.license: MITmetadata: author: beth-team version: "1.0.0" registry: https://ui.shadcn.com---# shadcn/ui Component SkillThis skill covers shadcn/ui component patterns, installation, customization, and MCP server integration for AI-assisted development.## What is shadcn/ui?shadcn/ui is **not a component library**—it's a code distribution system. You own the code. Components are copied into your project where you can customize them freely.Key principles:- **Open Code**: You get the actual component source code- **Composition**: Components share a consistent, composable API- **Beautiful Defaults**: Carefully designed defaults that work together- **AI-Ready**: Open code for LLMs to read, understand, and modify## MCP Server IntegrationThe shadcn MCP server enables AI assistants to interact directly with component registries.### Configuration**VS Code (GitHub Copilot):**```bashnpx shadcn@latest mcp init --client vscode```Creates `.vscode/mcp.json`:```json{ "servers": { "shadcn": { "command": "npx", "args": ["shadcn@latest", "mcp"] } }}```**Other Clients:**- Claude Code: `npx shadcn@latest mcp init --client claude`- Cursor: `npx shadcn@latest mcp init --client cursor`- Codex: Manual config in `~/.codex/config.toml`### MCP Tools| Tool | Purpose | Example Use ||------|---------|-------------|| `get_project_registries` | List configured registries | Verify setup || `search_items_in_registries` | Search by name/function | "Find data table component" || `list_items_in_registries` | Browse all components | Explore options || `get_item_details_from_registries` | Get full source code | Understand implementation || `get_item_examples_from_registries` | Get usage examples | Learn patterns || `add_items_to_project` | Install components | "Add button and card" |### Example MCP Prompts```# Discovery"Show me all available components in the shadcn registry""What form components are available?""Find me a date picker"# Details"Show me the code for the dialog component""Get examples of the data-table component""What are the variants for the button component?"# Installation"Add button, card, and dialog to this project""Install all the form components""Add the chart component"```## Project Setup### Initialize a new project```bashnpx shadcn@latest init```This creates:- `components.json` - Configuration- `lib/utils.ts` - Utility functions (cn helper)- `tailwind.config.ts` - Tailwind configuration- `globals.css` - CSS variables for theming### Configuration (components.json)```json{ "$schema": "https://ui.shadcn.com/schema.json", "style": "new-york", "rsc": true, "tsx": true, "tailwind": { "config": "tailwind.config.ts", "css": "app/globals.css", "baseColor": "zinc", "cssVariables": true }, "aliases": { "components": "@/components", "utils": "@/lib/utils", "ui": "@/components/ui" }, "registries": { "@shadcn": "https://ui.shadcn.com/r/{name}.json" }}```### Add components```bash# Single componentnpx shadcn@latest add button# Multiple componentsnpx shadcn@latest add button card dialog# All componentsnpx shadcn@latest add --all```## Component Patterns### Basic Usage```tsximport { Button } from "@/components/ui/button";import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";export function Example() { return ( <Card> <CardHeader> <CardTitle>Hello World</CardTitle> </CardHeader> <CardContent> <Button>Click Me</Button> </CardContent> </Card> );}```### Composition Patternshadcn/ui components are composable by design:```tsx// Dialog with custom trigger<Dialog> <DialogTrigger asChild> <Button variant="outline">Open Settings</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Settings</DialogTitle> <DialogDescription>Configure your preferences.</DialogDescription> </DialogHeader> {/* Content */} <DialogFooter> <Button type="submit">Save</Button> </DialogFooter> </DialogContent></Dialog>```### The cn() UtilityThe `cn()` helper merges Tailwind classes intelligently:```tsximport { cn } from "@/lib/utils";function MyComponent({ className, ...props }) { return ( <div className={cn("base-styles", className)} {...props} /> );}```Implementation (uses clsx + tailwind-merge):```tsimport { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs));}```### Component Variants with cvaUse `class-variance-authority` for component variants:```tsximport { cva, type VariantProps } from "class-variance-authority";import { cn } from "@/lib/utils";const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground", secondary: "border-transparent bg-secondary text-secondary-foreground", destructive: "border-transparent bg-destructive text-destructive-foreground", outline: "text-foreground", }, }, defaultVariants: { variant: "default", }, });interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}function Badge({ className, variant, ...props }: BadgeProps) { return ( <div className={cn(badgeVariants({ variant }), className)} {...props} /> );}```## Theming### CSS Variablesshadcn/ui uses CSS variables for theming:```css:root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; --primary: 222.2 47.4% 11.2%; --primary-foreground: 210 40% 98%; /* ... more variables */}.dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; /* ... dark mode overrides */}```### Using Theme Colors```tsx// In Tailwind classes<div className="bg-background text-foreground"> <button className="bg-primary text-primary-foreground"> Primary Button </button></div>// Accessing in JSconst styles = { backgroundColor: 'hsl(var(--background))', color: 'hsl(var(--foreground))',};```### Dark ModeConfigure dark mode in `tailwind.config.ts`:```tsmodule.exports = { darkMode: ["class"], // ...}```Toggle with a theme provider:```tsx'use client';import { ThemeProvider } from "next-themes";export function Providers({ children }) { return ( <ThemeProvider attribute="class" defaultTheme="system" enableSystem> {children} </ThemeProvider> );}```## Form Patterns### With React Hook Form + Zod```tsx'use client';import { useForm } from "react-hook-form";import { zodResolver } from "@hookform/resolvers/zod";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 schema = z.object({ email: z.string().email(), password: z.string().min(8),});export function LoginForm() { const form = useForm({ resolver: zodResolver(schema), defaultValues: { email: "", password: "" }, }); async function onSubmit(values: z.infer<typeof schema>) { // Handle submission } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input type="email" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="password" render={({ field }) => ( <FormItem> <FormLabel>Password</FormLabel> <FormControl> <Input type="password" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit">Login</Button> </form> </Form> );}```## Data Display### DataTable Pattern```tsximport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow,} from "@/components/ui/table";interface Column<T> { header: string; accessor: keyof T | ((row: T) => React.ReactNode);}interface DataTableProps<T> { columns: Column<T>[]; data: T[];}export function DataTable<T extends { id: string }>({ columns, data }: DataTableProps<T>) { return ( <Table> <TableHeader> <TableRow> {columns.map((col, i) => ( <TableHead key={i}>{col.header}</TableHead> ))} </TableRow> </TableHeader> <TableBody> {data.map((row) => ( <TableRow key={row.id}> {columns.map((col, i) => ( <TableCell key={i}> {typeof col.accessor === 'function' ? col.accessor(row) : String(row[col.accessor])} </TableCell> ))} </TableRow> ))} </TableBody> </Table> );}```## Accessibilityshadcn/ui components are built on Radix UI primitives which handle:- Keyboard navigation- Focus management- ARIA attributes- Screen reader announcements### Ensure Accessibility in Custom Code```tsx// Always provide accessible labels<Button aria-label="Close dialog"> <X className="h-4 w-4" /></Button>// Use sr-only for screen reader text<span className="sr-only">Loading</span>// Handle focus management<Dialog onOpenChange={(open) => { if (!open) { // Return focus to trigger triggerRef.current?.focus(); }}}>```## Best Practices### DO- ✅ Accept `className` prop for composition- ✅ Use `cn()` to merge classes- ✅ Forward refs when needed- ✅ Keep components focused and composable- ✅ Use TypeScript for props- ✅ Add loading/disabled states### DON'T- ❌ Modify components in `ui/` directly for one-off changes- ❌ Override styles with `!important`- ❌ Create wrapper components when composition works- ❌ Skip accessibility testing- ❌ Use deprecated patterns### When to Customize vs Compose**Compose** (use as-is with props):```tsx<Button size="lg" variant="outline">Large Outline</Button>```**Extend** (create specialized version):```tsx// components/IconButton.tsximport { Button, ButtonProps } from "@/components/ui/button";import { cn } from "@/lib/utils";interface IconButtonProps extends ButtonProps { icon: React.ReactNode;}export function IconButton({ icon, className, ...props }: IconButtonProps) { return ( <Button className={cn("p-2", className)} {...props}> {icon} </Button> );}```**Customize** (modify source when pattern doesn't exist):- Fork `ui/button.tsx` only for project-wide changes- Document the customization## Common Components Reference| Component | Use For ||-----------|---------|| `Button` | Actions, form submissions || `Card` | Content containers || `Dialog` | Modal interactions || `Sheet` | Side panels || `Form` | Form containers with validation || `Input`, `Textarea` | Text inputs || `Select` | Dropdowns || `Checkbox`, `Radio`, `Switch` | Toggles || `Table` | Data display || `Tabs` | Content organization || `Toast` | Notifications || `Tooltip` | Contextual info || `Command` | Command palettes || `DropdownMenu` | Action menus || `NavigationMenu` | Site navigation || `Avatar` | User images || `Badge` | Status indicators || `Skeleton` | Loading placeholders |## Troubleshooting### Component not found```bash# Check available componentsnpx shadcn@latest add --help# Search via MCP"List all components in the shadcn registry"```### Styling conflicts- Ensure Tailwind is properly configured- Check CSS variable definitions- Use `cn()` for class merging### Dark mode not working- Verify `darkMode: ["class"]` in tailwind.config- Check ThemeProvider is wrapping your app- Ensure CSS variables have dark mode overrides### TypeScript errors- Run `npx shadcn@latest add [component]` to get latest types- Check component is exported from `ui/` index````
stephschofield/beth/tree/main/.github/skills/shadcn-ui commit 0e9d415fd8
Frequently asked questions
Run npx skillmds@latest add stephschofield/shadcn-ui in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
````skill It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
stephschofield (@stephschofield) published this skill. Their other Agent Skills are listed on their SkillMD profile.