shadcn/ui Component Patterns
Build accessible, customizable UI components with shadcn/ui, Radix UI, and Tailwind CSS.
Overview
- Components are copied into your project — you own and customize the code
- Built on Radix UI primitives for full accessibility
- Styled with Tailwind CSS and CSS variables for theming
- CLI-based installation:
npx shadcn@latest add <component>
When to Use
Activate when user requests involve:
- "Set up shadcn/ui", "initialize shadcn", "add shadcn components"
- "Install button/input/form/dialog/card/select/toast/table/chart"
- "React Hook Form", "Zod validation", "form with validation"
- "accessible components", "Radix UI", "Tailwind theme"
- "shadcn button", "shadcn dialog", "shadcn sheet", "shadcn table"
- "dark mode", "CSS variables", "custom theme"
- "charts with Recharts", "bar chart", "line chart", "pie chart"
Quick Reference
Available Components
| Component |
Install Command |
Description |
button |
npx shadcn@latest add button |
Variants: default, destructive, outline, secondary, ghost, link |
input |
npx shadcn@latest add input |
Text input field |
form |
npx shadcn@latest add form |
React Hook Form integration with validation |
card |
npx shadcn@latest add card |
Container with header, content, footer |
dialog |
npx shadcn@latest add dialog |
Modal overlay |
sheet |
npx shadcn@latest add sheet |
Slide-over panel (top/right/bottom/left) |
select |
npx shadcn@latest add select |
Dropdown select |
toast |
npx shadcn@latest add toast |
Notification toasts |
table |
npx shadcn@latest add table |
Data table |
menubar |
npx shadcn@latest add menubar |
Desktop-style menubar |
chart |
npx shadcn@latest add chart |
Recharts wrapper with theming |
textarea |
npx shadcn@latest add textarea |
Multi-line text input |
checkbox |
npx shadcn@latest add checkbox |
Checkbox input |
label |
npx shadcn@latest add label |
Accessible form label |
Instructions
Initialize Project
# New Next.js project
npx create-next-app@latest my-app --typescript --tailwind --eslint --app
cd my-app
npx shadcn@latest init
# Existing project
npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react
npx shadcn@latest init
# Install components
npx shadcn@latest add button input form card dialog select toast
Basic Component Usage
// Button with variants and sizes
import { Button } from "@/components/ui/button"
<Button variant="default">Default</Button>
<Button variant="destructive" size="sm">Delete</Button>
<Button variant="outline" disabled>Loading...</Button>
Form with Zod Validation
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
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 formSchema = z.object({
email: z.string().email("Invalid email"),
password: z.string().min(8, "Password must be at least 8 characters"),
})
export function LoginForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: { email: "", password: "" },
})
return (
<Form {...form}>
<form className="space-y-4">
<FormField name="email" control={form.control} render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl><Input type="email" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<FormField name="password" control={form.control} render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl><Input type="password" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<Button type="submit">Login</Button>
</form>
</Form>
)
}
See references/forms-and-validation.md for advanced multi-field forms, contact forms with API submission, and login card patterns.
Dialog (Modal)
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Profile</DialogTitle>
</DialogHeader>
{/* content */}
</DialogContent>
</Dialog>
Toast Notification
// 1. Add <Toaster /> to app/layout.tsx
import { Toaster } from "@/components/ui/toaster"
// 2. Use in components
import { useToast } from "@/components/ui/use-toast"
const { toast } = useToast()
toast({ title: "Success", description: "Changes saved." })
toast({ variant: "destructive", title: "Error", description: "Something went wrong." })
Bar Chart
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
const chartConfig = {
desktop: { label: "Desktop", color: "var(--chart-1)" },
} satisfies import("@/components/ui/chart").ChartConfig
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
<BarChart data={data}>
<CartesianGrid vertical={false} />
<XAxis dataKey="month" />
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
<ChartTooltip content={<ChartTooltipContent />} />
</BarChart>
</ChartContainer>
See references/charts-components.md for Line, Area, and Pie chart examples.
Examples
Login Form with Validation
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
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 formSchema = z.object({
email: z.string().email("Invalid email"),
password: z.string().min(8, "Min 8 characters"),
})
export function LoginForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: { email: "", password: "" },
})
return (
<Form {...form}>
<form className="space-y-4">
<FormField name="email" control={form.control} render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl><Input type="email" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<FormField name="password" control={form.control} render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl><Input type="password" {...field} /></FormControl>
<FormMessage />
</FormItem>
)} />
<Button type="submit">Login</Button>
</form>
</Form>
)
}
Data Table with Actions
import { ColumnDef } from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { DataTable } from "@/components/ui/data-table"
const columns: ColumnDef<User>[] = [
{ id: "select", header: ({ table }) => (
<Checkbox checked={table.getIsAllPageRowsSelected()} />
), cell: ({ row }) => (
<Checkbox checked={row.getIsSelected()} />
)},
{ accessorKey: "name", header: "Name" },
{ accessorKey: "email", header: "Email" },
{ id: "actions", cell: ({ row }) => (
<Button variant="ghost" size="sm">Edit</Button>
)},
]
Dialog with Form
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Add User</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Add New User</DialogTitle>
</DialogHeader>
{/* <LoginForm /> */}
</DialogContent>
</Dialog>
Toast Notifications
import { useToast } from "@/components/ui/use-toast"
import { Button } from "@/components/ui/button"
const { toast } = useToast()
toast({ title: "Saved", description: "Changes saved successfully." })
toast({ variant: "destructive", title: "Error", description: "Failed to save." })
Best Practices
- Accessibility: Use Radix UI primitives — ARIA attributes are built in
- Client Components: Add
"use client" for interactive components (hooks, events)
- Type Safety: Use TypeScript and Zod schemas for form validation
- Theming: Configure CSS variables in
globals.css for consistent design
- Customization: Modify component files directly — you own the code
- Path Aliases: Ensure
@ alias is configured in tsconfig.json
- Registry Security: Only install components from trusted registries; review generated code before production use
- Dark Mode: Set up with CSS variables strategy and
next-themes
- Forms: Always use
Form, FormField, FormItem, FormLabel, FormMessage together
- Toaster: Add
<Toaster /> once to root layout
Constraints and Warnings
- Not an NPM Package: Components are copied to your project; they are not a versioned dependency
- Registry Security: Components from
npx shadcn@latest add are fetched remotely; always verify the registry source is trusted before installation
- Client Components: Most interactive components require
"use client" directive
- Radix Dependencies: Ensure all
@radix-ui packages are installed
- Tailwind Required: Components rely on Tailwind CSS utilities
- Path Aliases: Configure
@ alias in tsconfig.json for imports
References
Consult these files for detailed patterns and code examples:
- references/setup-and-configuration.md — Full installation, tsconfig, tailwind config, CSS variables
- references/ui-components.md — Button, Input, Card, Dialog, Sheet, Select, Toast, Table, Menubar
- references/forms-and-validation.md — React Hook Form + Zod, advanced forms, login card, contact form
- references/charts-components.md — Bar, Line, Area, Pie charts with ChartContainer and theming
- references/nextjs-integration.md — App Router, Server/Client Components, dark mode, metadata
- references/customization.md — Custom variants, CSS variables, cn() utility, extending components
1---2name: shadcn-ui-73description: Provides complete shadcn/ui component library patterns including installation, configuration, and implementation of accessible React components. Use when setting up shadcn/ui, installing components, building forms with React Hook Form and Zod, customizing themes with Tailwind CSS, or implementing UI patterns like buttons, dialogs, dropdowns, tables, and complex form layouts.4---5
6# shadcn/ui Component Patterns
7
8Build accessible, customizable UI components with shadcn/ui, Radix UI, and Tailwind CSS.
9
10## Overview
11
12- Components are **copied into your project** — you own and customize the code
13- Built on **Radix UI** primitives for full accessibility
14- Styled with **Tailwind CSS** and CSS variables for theming
15- CLI-based installation: `npx shadcn@latest add <component>`
16
17## When to Use
18
19Activate when user requests involve:
20- "Set up shadcn/ui", "initialize shadcn", "add shadcn components"
21- "Install button/input/form/dialog/card/select/toast/table/chart"
22- "React Hook Form", "Zod validation", "form with validation"
23- "accessible components", "Radix UI", "Tailwind theme"
24- "shadcn button", "shadcn dialog", "shadcn sheet", "shadcn table"
25- "dark mode", "CSS variables", "custom theme"
26- "charts with Recharts", "bar chart", "line chart", "pie chart"
27
28## Quick Reference
29
30### Available Components
31
32| Component | Install Command | Description |
33|-----------|----------------|-------------|
34| `button` | `npx shadcn@latest add button` | Variants: default, destructive, outline, secondary, ghost, link |
35| `input` | `npx shadcn@latest add input` | Text input field |
36| `form` | `npx shadcn@latest add form` | React Hook Form integration with validation |
37| `card` | `npx shadcn@latest add card` | Container with header, content, footer |
38| `dialog` | `npx shadcn@latest add dialog` | Modal overlay |
39| `sheet` | `npx shadcn@latest add sheet` | Slide-over panel (top/right/bottom/left) |
40| `select` | `npx shadcn@latest add select` | Dropdown select |
41| `toast` | `npx shadcn@latest add toast` | Notification toasts |
42| `table` | `npx shadcn@latest add table` | Data table |
43| `menubar` | `npx shadcn@latest add menubar` | Desktop-style menubar |
44| `chart` | `npx shadcn@latest add chart` | Recharts wrapper with theming |
45| `textarea` | `npx shadcn@latest add textarea` | Multi-line text input |
46| `checkbox` | `npx shadcn@latest add checkbox` | Checkbox input |
47| `label` | `npx shadcn@latest add label` | Accessible form label |
48
49## Instructions
50
51### Initialize Project
52
53```bash
54# New Next.js project
55npx create-next-app@latest my-app --typescript --tailwind --eslint --app
56cd my-app
57npx shadcn@latest init
58
59# Existing project
60npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react
61npx shadcn@latest init
62
63# Install components
64npx shadcn@latest add button input form card dialog select toast
65```
66
67### Basic Component Usage
68
69```tsx
70// Button with variants and sizes
71import { Button } from "@/components/ui/button"
72
73<Button variant="default">Default</Button>
74<Button variant="destructive" size="sm">Delete</Button>
75<Button variant="outline" disabled>Loading...</Button>
76```
77
78### Form with Zod Validation
79
80```tsx
81"use client"
82
83import { zodResolver } from "@hookform/resolvers/zod"
84import { useForm } from "react-hook-form"
85import { z } from "zod"
86import { Button } from "@/components/ui/button"
87import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
88import { Input } from "@/components/ui/input"
89
90const formSchema = z.object({
91 email: z.string().email("Invalid email"),
92 password: z.string().min(8, "Password must be at least 8 characters"),
93})
94
95export function LoginForm() {
96 const form = useForm<z.infer<typeof formSchema>>({
97 resolver: zodResolver(formSchema),
98 defaultValues: { email: "", password: "" },
99 })
100
101 return (
102 <Form {...form}>
103 <form onSubmit={form.handleSubmit(console.log)} className="space-y-4">
104 <FormField name="email" control={form.control} render={({ field }) => (
105 <FormItem>
106 <FormLabel>Email</FormLabel>
107 <FormControl><Input type="email" {...field} /></FormControl>
108 <FormMessage />
109 </FormItem>
110 )} />
111 <FormField name="password" control={form.control} render={({ field }) => (
112 <FormItem>
113 <FormLabel>Password</FormLabel>
114 <FormControl><Input type="password" {...field} /></FormControl>
115 <FormMessage />
116 </FormItem>
117 )} />
118 <Button type="submit">Login</Button>
119 </form>
120 </Form>
121 )
122}
123```
124
125See [references/forms-and-validation.md](references/forms-and-validation.md) for advanced multi-field forms, contact forms with API submission, and login card patterns.
126
127### Dialog (Modal)
128
129```tsx
130import { Button } from "@/components/ui/button"
131import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
132
133<Dialog>
134 <DialogTrigger asChild>
135 <Button variant="outline">Open</Button>
136 </DialogTrigger>
137 <DialogContent>
138 <DialogHeader>
139 <DialogTitle>Edit Profile</DialogTitle>
140 </DialogHeader>
141 {/* content */}
142 </DialogContent>
143</Dialog>
144```
145
146### Toast Notification
147
148```tsx
149// 1. Add <Toaster /> to app/layout.tsx
150import { Toaster } from "@/components/ui/toaster"
151
152// 2. Use in components
153import { useToast } from "@/components/ui/use-toast"
154
155const { toast } = useToast()
156toast({ title: "Success", description: "Changes saved." })
157toast({ variant: "destructive", title: "Error", description: "Something went wrong." })
158```
159
160### Bar Chart
161
162```tsx
163import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
164import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
165
166const chartConfig = {
167 desktop: { label: "Desktop", color: "var(--chart-1)" },
168} satisfies import("@/components/ui/chart").ChartConfig
169
170<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
171 <BarChart data={data}>
172 <CartesianGrid vertical={false} />
173 <XAxis dataKey="month" />
174 <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
175 <ChartTooltip content={<ChartTooltipContent />} />
176 </BarChart>
177</ChartContainer>
178```
179
180See [references/charts-components.md](references/charts-components.md) for Line, Area, and Pie chart examples.
181
182## Examples
183
184### Login Form with Validation
185```tsx
186"use client"
187import { zodResolver } from "@hookform/resolvers/zod"
188import { useForm } from "react-hook-form"
189import { z } from "zod"
190import { Button } from "@/components/ui/button"
191import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
192import { Input } from "@/components/ui/input"
193
194const formSchema = z.object({
195 email: z.string().email("Invalid email"),
196 password: z.string().min(8, "Min 8 characters"),
197})
198
199export function LoginForm() {
200 const form = useForm<z.infer<typeof formSchema>>({
201 resolver: zodResolver(formSchema),
202 defaultValues: { email: "", password: "" },
203 })
204
205 return (
206 <Form {...form}>
207 <form onSubmit={form.handleSubmit(console.log)} className="space-y-4">
208 <FormField name="email" control={form.control} render={({ field }) => (
209 <FormItem>
210 <FormLabel>Email</FormLabel>
211 <FormControl><Input type="email" {...field} /></FormControl>
212 <FormMessage />
213 </FormItem>
214 )} />
215 <FormField name="password" control={form.control} render={({ field }) => (
216 <FormItem>
217 <FormLabel>Password</FormLabel>
218 <FormControl><Input type="password" {...field} /></FormControl>
219 <FormMessage />
220 </FormItem>
221 )} />
222 <Button type="submit">Login</Button>
223 </form>
224 </Form>
225 )
226}
227```
228
229### Data Table with Actions
230```tsx
231import { ColumnDef } from "@tanstack/react-table"
232import { Button } from "@/components/ui/button"
233import { Checkbox } from "@/components/ui/checkbox"
234import { DataTable } from "@/components/ui/data-table"
235
236const columns: ColumnDef<User>[] = [
237 { id: "select", header: ({ table }) => (
238 <Checkbox checked={table.getIsAllPageRowsSelected()} />
239 ), cell: ({ row }) => (
240 <Checkbox checked={row.getIsSelected()} />
241 )},
242 { accessorKey: "name", header: "Name" },
243 { accessorKey: "email", header: "Email" },
244 { id: "actions", cell: ({ row }) => (
245 <Button variant="ghost" size="sm">Edit</Button>
246 )},
247]
248```
249
250### Dialog with Form
251```tsx
252import { Button } from "@/components/ui/button"
253import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
254
255<Dialog>
256 <DialogTrigger asChild>
257 <Button variant="outline">Add User</Button>
258 </DialogTrigger>
259 <DialogContent>
260 <DialogHeader>
261 <DialogTitle>Add New User</DialogTitle>
262 </DialogHeader>
263 {/* <LoginForm /> */}
264 </DialogContent>
265</Dialog>
266```
267
268### Toast Notifications
269```tsx
270import { useToast } from "@/components/ui/use-toast"
271import { Button } from "@/components/ui/button"
272
273const { toast } = useToast()
274
275toast({ title: "Saved", description: "Changes saved successfully." })
276toast({ variant: "destructive", title: "Error", description: "Failed to save." })
277```
278
279## Best Practices
280
281- **Accessibility**: Use Radix UI primitives — ARIA attributes are built in
282- **Client Components**: Add `"use client"` for interactive components (hooks, events)
283- **Type Safety**: Use TypeScript and Zod schemas for form validation
284- **Theming**: Configure CSS variables in `globals.css` for consistent design
285- **Customization**: Modify component files directly — you own the code
286- **Path Aliases**: Ensure `@` alias is configured in `tsconfig.json`
287- **Registry Security**: Only install components from trusted registries; review generated code before production use
288- **Dark Mode**: Set up with CSS variables strategy and `next-themes`
289- **Forms**: Always use `Form`, `FormField`, `FormItem`, `FormLabel`, `FormMessage` together
290- **Toaster**: Add `<Toaster />` once to root layout
291
292## Constraints and Warnings
293
294- **Not an NPM Package**: Components are copied to your project; they are not a versioned dependency
295- **Registry Security**: Components from `npx shadcn@latest add` are fetched remotely; always verify the registry source is trusted before installation
296- **Client Components**: Most interactive components require `"use client"` directive
297- **Radix Dependencies**: Ensure all `@radix-ui` packages are installed
298- **Tailwind Required**: Components rely on Tailwind CSS utilities
299- **Path Aliases**: Configure `@` alias in `tsconfig.json` for imports
300
301## References
302
303Consult these files for detailed patterns and code examples:
304
305- **[references/setup-and-configuration.md](references/setup-and-configuration.md)** — Full installation, tsconfig, tailwind config, CSS variables
306- **[references/ui-components.md](references/ui-components.md)** — Button, Input, Card, Dialog, Sheet, Select, Toast, Table, Menubar
307- **[references/forms-and-validation.md](references/forms-and-validation.md)** — React Hook Form + Zod, advanced forms, login card, contact form
308- **[references/charts-components.md](references/charts-components.md)** — Bar, Line, Area, Pie charts with ChartContainer and theming
309- **[references/nextjs-integration.md](references/nextjs-integration.md)** — App Router, Server/Client Components, dark mode, metadata
310- **[references/customization.md](references/customization.md)** — Custom variants, CSS variables, cn() utility, extending components