shadcn-ui

Build 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.

7 stars

Best use case

shadcn-ui is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Build 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.

Teams using shadcn-ui should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/shadcn-ui/SKILL.md --create-dirs "https://raw.githubusercontent.com/wpank/ai/main/skills/frontend/shadcn-ui/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/shadcn-ui/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How shadcn-ui Compares

Feature / Agentshadcn-uiStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Build 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.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# shadcn/ui Component Patterns

Expert guide for building accessible, customizable UI components with shadcn/ui.


## Installation

### OpenClaw / Moltbot / Clawbot

```bash
npx clawhub@latest install shadcn-ui
```


## WHEN

- Setting up a new project with shadcn/ui
- Installing or configuring individual components
- Building forms with React Hook Form and Zod validation
- Creating accessible UI components (buttons, dialogs, dropdowns, sheets)
- Customizing component styling with Tailwind CSS
- Implementing design systems with shadcn/ui
- Building Next.js applications with TypeScript

## What is 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).

## Quick Start

```bash
# 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
```

## Core Concepts

### The `cn` Utility

Merges Tailwind classes with conflict resolution — used in every component:

```tsx
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
```

### Class Variance Authority (CVA)

Manages component variants — the pattern behind every shadcn/ui component:

```tsx
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" },
  }
)
```

## Essential Components

### Button

```tsx
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>
```

### Forms with Validation

The standard pattern: Zod schema + React Hook Form + shadcn Form components.

```bash
npx shadcn@latest add form input select checkbox textarea
```

```tsx
"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>
  )
}
```

### Dialog & Sheet

```tsx
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>
```

### Card

```tsx
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>
```

### Toast Notifications

```tsx
// 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>,
})
```

### Table

```tsx
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>
```

## Theming

shadcn/ui uses CSS variables in HSL format. Configure in `globals.css`:

```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.

## Customizing Components

Since you own the code, modify components directly:

```tsx
// 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 Reference

| 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 |

## Next.js Integration

### App Router Setup

For Next.js 13+ with App Router, ensure interactive components use `"use client"`:

```tsx
// src/components/ui/button.tsx
"use client"

import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
// ... rest of component
```

### Layout Integration

Add the Toaster to your root layout:

```tsx
// 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>
  )
}
```

### Server Components

Most shadcn/ui components need `"use client"`. For Server Components, wrap them in a client component or use them in client component children.

## CLI Reference

```bash
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
```

## Best Practices

| 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 Do

| 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 |

## References

- [Learning Guide](references/learn.md) — progression from basics to advanced patterns
- [Extended Components](references/extended-components.md) — Terminal, Dock, Charts, animations, custom hooks
- [Official Docs](https://ui.shadcn.com) | [Radix UI](https://www.radix-ui.com) | [React Hook Form](https://react-hook-form.com) | [Zod](https://zod.dev)

Related Skills

tailwind-v4-+-shadcn/ui-stack

7
from wpank/ai

No description provided.

schema-markup

7
from wpank/ai

Add, fix, or optimize schema markup and structured data. Use when the user mentions schema markup, structured data, JSON-LD, rich snippets, schema.org, FAQ schema, product schema, review schema, or breadcrumb schema.

prompt-engineering

7
from wpank/ai

Master advanced prompt engineering techniques to maximize LLM performance, reliability, and controllability in production. Use when optimizing prompts, improving LLM outputs, designing production prompt templates, or building AI-powered features.

professional-communication

7
from wpank/ai

Write effective professional messages for software teams. Use when drafting emails, Slack/Teams messages, meeting agendas, status updates, or translating technical concepts for non-technical audiences. Triggers on email, slack, teams, message, meeting agenda, status update, stakeholder communication, escalation, jargon translation.

persona-docs

7
from wpank/ai

Create persona documentation for a product or codebase. Use when asked to create persona docs, document target users, define user journeys, document onboarding flows, or when starting a new product and needing to define its audience. Persona docs should be the first documentation created for any product.

mermaid-diagrams

7
from wpank/ai

Create software diagrams using Mermaid syntax. Use when users need to create, visualize, or document software through diagrams including class diagrams, sequence diagrams, flowcharts, ERDs, C4 architecture diagrams, state diagrams, git graphs, and other diagram types. Triggers include requests to diagram, visualize, model, map out, or show the flow of a system.

game-changing-features

7
from wpank/ai

Find 10x product opportunities and high-leverage improvements. Use when the user wants strategic product thinking, mentions 10x, wants to find high-impact features, or asks what would make a product dramatically more valuable.

clear-writing

7
from wpank/ai

Write clear, concise prose for humans — documentation, READMEs, API docs, commit messages, error messages, UI text, reports, and explanations. Combines Strunk's rules for clearer prose with technical documentation patterns, structure templates, and review checklists.

brainstorming

7
from wpank/ai

Explore ideas before implementation through collaborative dialogue. Use before any creative work — creating features, building components, adding functionality, or modifying behavior. Turns ideas into fully formed designs and specs through structured conversation.

Article Illustrator

7
from wpank/ai

When the user wants to add illustrations to an article or blog post. Triggers on: "illustrate article", "add images to article", "generate illustrations", "article images", or requests to visually enhance written content. Analyzes article structure, identifies positions for visual aids, and generates illustrations using a Type x Style two-dimension approach.

subagent-driven-development

7
from wpank/ai

Execute implementation plans by dispatching a fresh subagent per task with two-stage review (spec compliance then code quality). Use when you have an implementation plan with mostly independent tasks and want high-quality, fast iteration within a single session.

skill-judge

7
from wpank/ai

Evaluate Agent Skill quality against official specifications. Use when reviewing SKILL.md files, auditing skill packages, improving skill design, or checking if a skill follows best practices. Provides 8-dimension scoring (120 points) with actionable improvements. Triggers on review skill, evaluate skill, audit skill, improve skill, skill quality, SKILL.md review.