shadcnBuild accessible, customizable UIs with shadcn/ui, Radix UI, and Tailwind CSS. Use when setting up shadcn/ui, installing components, building forms with React Hook Form + Zod, customizing themes, or implementing component patterns.
Install via ClawdBot CLI:
clawdbot install wpank/shadcnExpert guide for building accessible, customizable UI components with shadcn/ui.
npx clawhub@latest install shadcn-ui
A collection of reusable components you copy into your project — not an npm package. You own the code. Built on Radix UI (accessibility) and Tailwind CSS (styling).
# New Next.js project
npx create-next-app@latest my-app --typescript --tailwind --eslint --app
cd my-app
npx shadcn@latest init
# Install components
npx shadcn@latest add button input form card dialog select toast
npx shadcn@latest add --all # or install everything
cn UtilityMerges Tailwind classes with conflict resolution — used in every component:
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Manages component variants — the pattern behind every shadcn/ui component:
import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: { variant: "default", size: "default" },
}
)
import { Button } from "@/components/ui/button"
import { Loader2 } from "lucide-react"
// Variants: default | destructive | outline | secondary | ghost | link
// Sizes: default | sm | lg | icon
<Button variant="outline" size="sm">Click me</Button>
// Loading state
<Button disabled>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Please wait
</Button>
// As link (uses Radix Slot)
<Button asChild>
<a href="/dashboard">Go to Dashboard</a>
</Button>
The standard pattern: Zod schema + React Hook Form + shadcn Form components.
npx shadcn@latest add form input select checkbox textarea
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import { Button } from "@/components/ui/button"
import {
Form, FormControl, FormDescription,
FormField, FormItem, FormLabel, FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
const formSchema = z.object({
username: z.string().min(2, "Username must be at least 2 characters."),
email: z.string().email("Please enter a valid email."),
role: z.enum(["admin", "user", "guest"]),
})
export function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: { username: "", email: "", role: "user" },
})
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField control={form.control} name="username" render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl><Input placeholder="shadcn" {...field} /></FormControl>
<FormDescription>Your public display name.</FormDescription>
<FormMessage />
</FormItem>
)} />
<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="role" render={({ field }) => (
<FormItem>
<FormLabel>Role</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger><SelectValue placeholder="Select a role" /></SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="user">User</SelectItem>
<SelectItem value="guest">Guest</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)} />
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
import {
Dialog, DialogContent, DialogDescription,
DialogFooter, DialogHeader, DialogTitle, DialogTrigger,
} from "@/components/ui/dialog"
import {
Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger,
} from "@/components/ui/sheet"
// Modal dialog
<Dialog>
<DialogTrigger asChild><Button variant="outline">Edit profile</Button></DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>Make changes here. Click save when done.</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">{/* form fields */}</div>
<DialogFooter><Button type="submit">Save changes</Button></DialogFooter>
</DialogContent>
</Dialog>
// Slide-over panel (side: "left" | "right" | "top" | "bottom")
<Sheet>
<SheetTrigger asChild><Button variant="outline">Open</Button></SheetTrigger>
<SheetContent side="right">
<SheetHeader><SheetTitle>Settings</SheetTitle></SheetHeader>
{/* content */}
</SheetContent>
</Sheet>
import {
Card, CardContent, CardDescription,
CardFooter, CardHeader, CardTitle,
} from "@/components/ui/card"
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Create project</CardTitle>
<CardDescription>Deploy your new project in one-click.</CardDescription>
</CardHeader>
<CardContent>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Project name" />
</div>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">Cancel</Button>
<Button>Deploy</Button>
</CardFooter>
</Card>
// 1. Add Toaster to root layout
import { Toaster } from "@/components/ui/toaster"
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}<Toaster /></body>
</html>
)
}
// 2. Use toast in components
import { useToast } from "@/components/ui/use-toast"
import { ToastAction } from "@/components/ui/toast"
const { toast } = useToast()
toast({ title: "Success", description: "Changes saved." })
toast({
variant: "destructive",
title: "Error",
description: "Something went wrong.",
action: <ToastAction altText="Try again">Try again</ToastAction>,
})
import {
Table, TableBody, TableCaption, TableCell,
TableHead, TableHeader, TableRow,
} from "@/components/ui/table"
const invoices = [
{ invoice: "INV001", status: "Paid", method: "Credit Card", amount: "$250.00" },
{ invoice: "INV002", status: "Pending", method: "PayPal", amount: "$150.00" },
]
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.status}</TableCell>
<TableCell>{invoice.method}</TableCell>
<TableCell className="text-right">{invoice.amount}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
shadcn/ui uses CSS variables in HSL format. Configure in globals.css:
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--destructive: 0 84.2% 60.2%;
--border: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
/* ... mirror all variables for dark mode */
}
}
Colors reference as hsl(var(--primary)) in Tailwind config. Change the CSS variables to retheme the entire app.
Since you own the code, modify components directly:
// Add a custom variant to button.tsx
const buttonVariants = cva("...", {
variants: {
variant: {
// ... existing variants
gradient: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
},
size: {
// ... existing sizes
xl: "h-14 rounded-md px-10 text-lg",
},
},
})
| Component | Install | Key Props |
|-----------|---------|-----------|
| Button | add button | variant, size, asChild |
| Input | add input | Standard HTML input props |
| Form | add form | React Hook Form + Zod integration |
| Card | add card | Header, Content, Footer composition |
| Dialog | add dialog | Modal with trigger pattern |
| Sheet | add sheet | Slide-over panel, side prop |
| Select | add select | Accessible dropdown |
| Toast | add toast | variant: "default" \| "destructive" |
| Table | add table | Header, Body, Row, Cell composition |
| Tabs | add tabs | defaultValue, trigger/content pairs |
| Accordion | add accordion | type: "single" \| "multiple" |
| Command | add command | Command palette / search |
| Dropdown Menu | add dropdown-menu | Context menus, action menus |
| Menubar | add menubar | Application menus with shortcuts |
For Next.js 13+ with App Router, ensure interactive components use "use client":
// src/components/ui/button.tsx
"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
// ... rest of component
Add the Toaster to your root layout:
// app/layout.tsx
import { Toaster } from "@/components/ui/toaster"
import "./globals.css"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className="min-h-screen bg-background font-sans antialiased">
{children}
<Toaster />
</body>
</html>
)
}
Most shadcn/ui components need "use client". For Server Components, wrap them in a client component or use them in client component children.
npx shadcn@latest init # Initialize project
npx shadcn@latest add [component] # Add specific component
npx shadcn@latest add --all # Add all components
npx shadcn@latest diff # Show upstream changes
| Practice | Details |
|----------|---------|
| Use TypeScript | All components ship with full type definitions |
| Zod for validation | Pair with React Hook Form for type-safe forms |
| asChild pattern | Use Radix Slot to render as different elements |
| Server Components | Most shadcn/ui components need "use client" |
| Consistent structure | Follow the existing component patterns when customizing |
| Accessibility | Radix primitives handle ARIA; don't override without reason |
| CSS variables | Theme via variables, not by editing component classes |
| Tree-shaking | Only install components you need — they're independent |
| Never | Why | Instead |
|-------|-----|---------|
| Install shadcn as npm package | It's not a package — it's source code you own | Use CLI: npx shadcn@latest add |
| Override ARIA attributes | Radix handles accessibility correctly | Trust the primitives |
| Use inline styles for theming | Defeats the design system | Modify CSS variables |
| Copy components from docs manually | May miss dependencies | Use CLI for proper installation |
| Mix component styles | Creates inconsistency | Follow CVA variant pattern |
Generated Mar 1, 2026
Develop a responsive admin dashboard for a SaaS platform using shadcn/ui components like tables, cards, and forms. Integrate React Hook Form with Zod validation for user settings and data input, ensuring accessibility and a consistent design system with Tailwind CSS customization.
Implement an accessible product page with shadcn/ui components such as buttons, dialogs for product details, and select dropdowns for variants. Use forms with validation for reviews or cart additions, leveraging Radix UI for seamless user interactions and mobile responsiveness.
Set up a secure patient portal with shadcn/ui for forms handling medical data, using Zod schemas for validation. Include components like sheets for navigation and toasts for notifications, ensuring compliance with accessibility standards and a clean, customizable UI.
Build a data visualization tool with shadcn/ui components such as cards and tables to display financial metrics. Incorporate forms for filtering data with React Hook Form, and use dialog components for detailed insights, all styled with Tailwind CSS for a professional look.
Create a high-conversion landing page for a startup using shadcn/ui for buttons, forms with validation for lead capture, and toast notifications. Customize the theme with Tailwind CSS to match brand identity, ensuring fast load times and accessibility across devices.
Offer custom website and application development services using shadcn/ui to build accessible, scalable UIs for clients. Charge per project or hourly, leveraging the skill to reduce development time and ensure high-quality, maintainable code with reusable components.
Develop and sell a SaaS product built with shadcn/ui, such as a dashboard or tool, using its components for rapid prototyping and consistent design. Monetize through subscription plans, with revenue generated from monthly or annual user fees.
Provide UI/UX design and development services to businesses, using shadcn/ui to create accessible and customizable interfaces for client projects. Offer packages for design systems, form implementations, and ongoing maintenance, billing on retainer or per project.
💬 Integration Tip
Start by initializing shadcn/ui in a Next.js project with TypeScript and Tailwind CSS, then use the 'cn' utility and CVA patterns for consistent styling. Integrate React Hook Form with Zod for form validation to streamline development.
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Expert frontend design guidelines for creating beautiful, modern UIs. Use when building landing pages, dashboards, or any user interface.
Use when building UI with shadcn/ui components, Tailwind CSS layouts, form patterns with react-hook-form and zod, theming, dark mode, sidebar layouts, mobile navigation, or any shadcn component question.
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when building web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Create distinctive, production-grade static sites with React, Tailwind CSS, and shadcn/ui — no mockups needed. Generates bold, memorable designs from plain text requirements with anti-AI-slop aesthetics, mobile-first responsive patterns, and single-file bundling. Use when building landing pages, marketing sites, portfolios, dashboards, or any static web UI. Supports both Vite (pure static) and Next.js (Vercel deploy) workflows.
AI skill for automated UI audits. Evaluate interfaces against proven UX principles for visual hierarchy, accessibility, cognitive load, navigation, and more. Based on Making UX Decisions by Tommy Geoco.