| name | shadcn |
| description | shadcn/ui component library with MCP server integration. Use when adding UI components, building forms, or working with shadcn primitives. Triggers on shadcn, @/components/ui, cn(), radix, cva, component variants. |
| triggers | shadcn, components/ui, cn(, radix, cva, buttonVariants, CardHeader, DialogContent |
The shadcn MCP server enables browsing, searching, and installing components via natural language. Always use it before manually creating components.
The shadcn MCP server is auto-configured via the devtools plugin. Use natural language:
"Show me all available components in the shadcn registry"
"Add button, dialog and card components"
"Create a contact form using shadcn components"
"Search for a date picker component"
Step 2: Import and use components
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { cn } from "@/lib/utils";
export function MyComponent() {
return (
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
</CardHeader>
<CardContent>
<Button variant="outline">Click me</Button>
</CardContent>
</Card>
);
}
See templates/cn-utility.ts.md for setup and usage patterns.
className={cn("base-class", isActive && "active-class")}
className={cn(buttonVariants({ variant, size }), className)}
See templates/cva-component.tsx.md for full pattern with placeholders.
import { cva, type VariantProps } from "class-variance-authority";
const buttonVariants = cva("base-styles", {
variants: {
variant: { default: "...", outline: "..." },
size: { default: "...", sm: "...", lg: "..." },
},
defaultVariants: { variant: "default", size: "default" },
});
See templates/compound-component.tsx.md for full pattern with placeholders.
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div data-slot="card" className={cn("rounded-xl border bg-card", className)} {...props} />
);
}
// CardHeader, CardTitle, CardContent, CardFooter follow same pattern
import { Slot } from "@radix-ui/react-slot";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
asChild?: boolean;
}
function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
// Usage: render as link
<Button asChild>
<a href="/dashboard">Go to Dashboard</a>
</Button>
| Component | Exports |
|---|---|
| Button | Button, buttonVariants |
| Card | Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter |
| Dialog | Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose |
| Select | Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel |
| Input | Input |
| Checkbox | Checkbox |
| Tabs | Tabs, TabsList, TabsTrigger, TabsContent |
| Accordion | Accordion, AccordionItem, AccordionTrigger, AccordionContent |
| Alert | Alert, AlertTitle, AlertDescription |
| Badge | Badge, badgeVariants |
| Sheet | Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription |
| Command | Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem |
| Popover | Popover, PopoverTrigger, PopoverContent |
| DropdownMenu | DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator |
See templates/theme-variables.css.md for complete light/dark theme setup.
// Reference in Tailwind
className = "bg-background text-foreground";
className = "bg-primary text-primary-foreground";
className = "bg-muted text-muted-foreground";
// Focus rings
className =
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2";
// Invalid state
className = "aria-invalid:border-destructive aria-invalid:ring-destructive/20";
// Disabled state
className = "disabled:pointer-events-none disabled:opacity-50";
// Screen reader only
className = "sr-only";
// Focus within group
className = "group-focus-within:ring-2";
ARIA attributes:
<Input aria-invalid={!!errors} aria-describedby={`${id}-error`} />
<div role="alert" id={`${id}-error`}>{error}</div>
import { ChevronDown, Check, X, Search, Settings } from "lucide-react";
// In components
<Button>
<Settings className="mr-2 h-4 w-4" />
Settings
</Button>
// Icon sizing pattern in shadcn components
className="[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0"
See templates/form-field.tsx.md for text/select field wrappers with accessibility.
<TextField label="Email" name="email" error={errors.email} required />
src/components/
├── ui/ # shadcn primitives (47+ components)
│ ├── button.tsx
│ ├── card.tsx
│ ├── dialog.tsx
│ ├── input.tsx
│ ├── select.tsx
│ └── ...
├── form/ # Form field wrappers
│ ├── text-field.tsx
│ ├── select-field.tsx
│ └── checkbox-field.tsx
└── [feature]/ # Feature-specific compositions
Banned:
- Inline styles (use Tailwind)
- Native form elements in styled UI (use custom components)
- Hardcoded colors (use CSS variables)
- Components > 150 lines
// Wrong - barrel imports
import { Button, Card } from "@/components/ui";
// Correct - direct imports
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
// Correct - cn utility className={cn("base-class", isActive && "active")}
</pitfall>
<pitfall name="native_form_elements">
```typescript
// Wrong - native select (can't be styled)
<select>{options}</select>
// Correct - shadcn Select
<Select>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{options.map(opt => <SelectItem key={opt} value={opt}>{opt}</SelectItem>)}
</SelectContent>
</Select>
- shadcn MCP server used for component discovery/installation
- Components imported from
@/components/ui/[name] -
cn()used for all className composition - CVA used for variant-based components
- CSS variables used for theming (no hardcoded colors)
- Accessibility attributes included (aria-*, focus-visible)
- Component under 150 lines