agents-templated 2.2.6 → 2.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1932 @@
1
+ ---
2
+ name: shadcn-ui
3
+ description: 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
+ allowed-tools: Read, Write, Bash, Edit, Glob
5
+ ---
6
+
7
+ # shadcn/ui Component Patterns
8
+
9
+ ## Overview
10
+
11
+ Expert guide for building accessible, customizable UI components with shadcn/ui, Radix UI, and Tailwind CSS. This skill provides comprehensive patterns for implementing production-ready components with full accessibility support.
12
+
13
+ ## Table of Contents
14
+
15
+ - [When to Use](#when-to-use)
16
+ - [Quick Start](#quick-start)
17
+ - [Installation & Setup](#installation--setup)
18
+ - [Project Configuration](#project-configuration)
19
+ - [Core Components](#core-components)
20
+ - [Button](#button-component)
21
+ - [Input & Form Fields](#input--form-fields)
22
+ - [Forms with Validation](#forms-with-validation)
23
+ - [Card](#card-component)
24
+ - [Dialog (Modal)](#dialog-modal-component)
25
+ - [Select (Dropdown)](#select-dropdown-component)
26
+ - [Sheet (Slide-over)](#sheet-slide-over-component)
27
+ - [Menubar & Navigation](#menubar--navigation)
28
+ - [Table](#table-component)
29
+ - [Toast Notifications](#toast-notifications)
30
+ - [Charts](#charts-component)
31
+ - [Advanced Patterns](#advanced-patterns)
32
+ - [Customization](#customization)
33
+ - [Next.js Integration](#nextjs-integration)
34
+ - [Best Practices](#best-practices)
35
+ - [Common Component Combinations](#common-component-combinations)
36
+
37
+ ## When to Use
38
+
39
+ - Setting up a new project with shadcn/ui
40
+ - Installing or configuring individual components
41
+ - Building forms with React Hook Form and Zod validation
42
+ - Creating accessible UI components (buttons, dialogs, dropdowns, sheets)
43
+ - Customizing component styling with Tailwind CSS
44
+ - Implementing design systems with shadcn/ui
45
+ - Building Next.js applications with TypeScript
46
+ - Creating complex layouts and data displays
47
+
48
+ ## Instructions
49
+
50
+ 1. **Initialize Project**: Run `npx shadcn@latest init` to configure shadcn/ui
51
+ 2. **Install Components**: Add components with `npx shadcn@latest add <component>`
52
+ 3. **Configure Theme**: Customize CSS variables in globals.css for theming
53
+ 4. **Import Components**: Use components from `@/components/ui/` directory
54
+ 5. **Customize as Needed**: Modify component code directly in your project
55
+ 6. **Add Form Validation**: Integrate React Hook Form with Zod schemas
56
+ 7. **Test Accessibility**: Verify ARIA attributes and keyboard navigation
57
+
58
+ ## Examples
59
+
60
+ ### Complete Form with Validation
61
+
62
+ ```tsx
63
+ "use client"
64
+
65
+ import { zodResolver } from "@hookform/resolvers/zod"
66
+ import { useForm } from "react-hook-form"
67
+ import { z } from "zod"
68
+ import { Button } from "@/components/ui/button"
69
+ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
70
+ import { Input } from "@/components/ui/input"
71
+
72
+ const formSchema = z.object({
73
+ email: z.string().email("Invalid email"),
74
+ password: z.string().min(8, "Password must be at least 8 characters"),
75
+ })
76
+
77
+ export function LoginForm() {
78
+ const form = useForm<z.infer<typeof formSchema>>({
79
+ resolver: zodResolver(formSchema),
80
+ defaultValues: { email: "", password: "" },
81
+ })
82
+
83
+ return (
84
+ <Form {...form}>
85
+ <form onSubmit={form.handleSubmit(console.log)} className="space-y-4">
86
+ <FormField name="email" render={({ field }) => (
87
+ <FormItem>
88
+ <FormLabel>Email</FormLabel>
89
+ <FormControl><Input type="email" {...field} /></FormControl>
90
+ <FormMessage />
91
+ </FormItem>
92
+ )} />
93
+ <Button type="submit">Login</Button>
94
+ </form>
95
+ </Form>
96
+ )
97
+ }
98
+ ```
99
+
100
+ ## Constraints and Warnings
101
+
102
+ - **Not an NPM Package**: Components are copied to your project; you own the code
103
+ - **Registry Security**: Components installed via `npx shadcn@latest add` are fetched from remote registries (e.g., `ui.shadcn.com`); always verify the registry source is trusted before installation, and review generated component code before use in production
104
+ - **Custom Registry Validation**: When configuring custom registries in `components.json`, only use trusted private registry URLs; never point to untrusted third-party registry endpoints as they could inject malicious code
105
+ - **Client Components**: Most components require "use client" directive
106
+ - **Radix Dependencies**: Ensure all `@`radix-ui packages are installed
107
+ - **Tailwind Required**: Components rely on Tailwind CSS utilities
108
+ - **TypeScript**: Designed for TypeScript projects; type definitions included
109
+ - **Path Aliases**: Configure @ alias in tsconfig.json for imports
110
+ - **Dark Mode**: Set up dark mode with CSS variables or class strategy
111
+
112
+ ## Quick Start
113
+
114
+ For new projects, use the automated setup:
115
+
116
+ ```bash
117
+ # Create Next.js project with shadcn/ui
118
+ npx create-next-app@latest my-app --typescript --tailwind --eslint --app
119
+ cd my-app
120
+ npx shadcn@latest init
121
+
122
+ # Install essential components
123
+ npx shadcn@latest add button input form card dialog select
124
+ ```
125
+
126
+ For existing projects:
127
+
128
+ ```bash
129
+ # Install dependencies
130
+ npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react
131
+
132
+ # Initialize shadcn/ui
133
+ npx shadcn@latest init
134
+ ```
135
+
136
+ ## What is shadcn/ui?
137
+
138
+ shadcn/ui is **not** a traditional component library or npm package. Instead:
139
+
140
+ - It's a **collection of reusable components** that you can copy into your project
141
+ - Components are **yours to customize** - you own the code
142
+ - Built with **Radix UI** primitives for accessibility
143
+ - Styled with **Tailwind CSS** utilities
144
+ - Includes CLI tool for easy component installation
145
+
146
+ ## Installation & Setup
147
+
148
+ ### Initial Setup
149
+
150
+ ```bash
151
+ # Initialize shadcn/ui in your project
152
+ npx shadcn@latest init
153
+ ```
154
+
155
+ During setup, you'll configure:
156
+ - TypeScript or JavaScript
157
+ - Style (Default, New York, etc.)
158
+ - Base color theme
159
+ - CSS variables or Tailwind CSS classes
160
+ - Component installation path
161
+
162
+ ### Installing Individual Components
163
+
164
+ ```bash
165
+ # Install a single component
166
+ npx shadcn@latest add button
167
+
168
+ # Install multiple components
169
+ npx shadcn@latest add button input form
170
+
171
+ # Install all components
172
+ npx shadcn@latest add --all
173
+ ```
174
+
175
+ ### Manual Installation
176
+
177
+ If you prefer manual setup:
178
+
179
+ ```bash
180
+ # Install dependencies for a specific component
181
+ npm install @radix-ui/react-slot
182
+
183
+ # Copy component code from ui.shadcn.com
184
+ # Place in src/components/ui/
185
+ ```
186
+
187
+ ## Project Configuration
188
+
189
+ ### Required Dependencies
190
+
191
+ ```json
192
+ {
193
+ "dependencies": {
194
+ "@radix-ui/react-accordion": "^1.1.2",
195
+ "@radix-ui/react-alert-dialog": "^1.0.5",
196
+ "@radix-ui/react-dialog": "^1.0.5",
197
+ "@radix-ui/react-dropdown-menu": "^2.0.6",
198
+ "@radix-ui/react-label": "^2.0.2",
199
+ "@radix-ui/react-select": "^2.0.0",
200
+ "@radix-ui/react-separator": "^1.0.3",
201
+ "@radix-ui/react-slot": "^1.0.2",
202
+ "@radix-ui/react-toast": "^1.1.5",
203
+ "class-variance-authority": "^0.7.0",
204
+ "clsx": "^2.0.0",
205
+ "lucide-react": "^0.294.0",
206
+ "tailwind-merge": "^2.0.0",
207
+ "tailwindcss-animate": "^1.0.7"
208
+ }
209
+ }
210
+ ```
211
+
212
+ ### TSConfig Configuration
213
+
214
+ ```json
215
+ {
216
+ "compilerOptions": {
217
+ "target": "es5",
218
+ "lib": ["dom", "dom.iterable", "es6"],
219
+ "allowJs": true,
220
+ "skipLibCheck": true,
221
+ "strict": true,
222
+ "forceConsistentCasingInFileNames": true,
223
+ "noEmit": true,
224
+ "esModuleInterop": true,
225
+ "module": "esnext",
226
+ "moduleResolution": "node",
227
+ "resolveJsonModule": true,
228
+ "isolatedModules": true,
229
+ "jsx": "preserve",
230
+ "incremental": true,
231
+ "plugins": [
232
+ {
233
+ "name": "next"
234
+ }
235
+ ],
236
+ "baseUrl": ".",
237
+ "paths": {
238
+ "@/components/*": ["./src/components/*"],
239
+ "@/lib/*": ["./src/lib/*"]
240
+ }
241
+ },
242
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
243
+ "exclude": ["node_modules"]
244
+ }
245
+ ```
246
+
247
+ ### Tailwind Configuration
248
+
249
+ ```js
250
+ // tailwind.config.js
251
+ /** @type {import('tailwindcss').Config} */
252
+ module.exports = {
253
+ darkMode: ["class"],
254
+ content: [
255
+ './pages/**/*.{ts,tsx}',
256
+ './components/**/*.{ts,tsx}',
257
+ './app/**/*.{ts,tsx}',
258
+ './src/**/*.{ts,tsx}',
259
+ ],
260
+ prefix: "",
261
+ theme: {
262
+ container: {
263
+ center: true,
264
+ padding: "2rem",
265
+ screens: {
266
+ "2xl": "1400px",
267
+ },
268
+ },
269
+ extend: {
270
+ colors: {
271
+ border: "hsl(var(--border))",
272
+ input: "hsl(var(--input))",
273
+ ring: "hsl(var(--ring))",
274
+ background: "hsl(var(--background))",
275
+ foreground: "hsl(var(--foreground))",
276
+ primary: {
277
+ DEFAULT: "hsl(var(--primary))",
278
+ foreground: "hsl(var(--primary-foreground))",
279
+ },
280
+ secondary: {
281
+ DEFAULT: "hsl(var(--secondary))",
282
+ foreground: "hsl(var(--secondary-foreground))",
283
+ },
284
+ destructive: {
285
+ DEFAULT: "hsl(var(--destructive))",
286
+ foreground: "hsl(var(--destructive-foreground))",
287
+ },
288
+ muted: {
289
+ DEFAULT: "hsl(var(--muted))",
290
+ foreground: "hsl(var(--muted-foreground))",
291
+ },
292
+ accent: {
293
+ DEFAULT: "hsl(var(--accent))",
294
+ foreground: "hsl(var(--accent-foreground))",
295
+ },
296
+ popover: {
297
+ DEFAULT: "hsl(var(--popover))",
298
+ foreground: "hsl(var(--popover-foreground))",
299
+ },
300
+ card: {
301
+ DEFAULT: "hsl(var(--card))",
302
+ foreground: "hsl(var(--card-foreground))",
303
+ },
304
+ },
305
+ borderRadius: {
306
+ lg: "var(--radius)",
307
+ md: "calc(var(--radius) - 2px)",
308
+ sm: "calc(var(--radius) - 4px)",
309
+ },
310
+ keyframes: {
311
+ "accordion-down": {
312
+ from: { height: "0" },
313
+ to: { height: "var(--radix-accordion-content-height)" },
314
+ },
315
+ "accordion-up": {
316
+ from: { height: "var(--radix-accordion-content-height)" },
317
+ to: { height: "0" },
318
+ },
319
+ },
320
+ animation: {
321
+ "accordion-down": "accordion-down 0.2s ease-out",
322
+ "accordion-up": "accordion-up 0.2s ease-out",
323
+ },
324
+ },
325
+ },
326
+ plugins: [require("tailwindcss-animate")],
327
+ }
328
+ ```
329
+
330
+ ### CSS Variables (globals.css)
331
+
332
+ ```css
333
+ @tailwind base;
334
+ @tailwind components;
335
+ @tailwind utilities;
336
+
337
+ @layer base {
338
+ :root {
339
+ --background: 0 0% 100%;
340
+ --foreground: 222.2 84% 4.9%;
341
+ --card: 0 0% 100%;
342
+ --card-foreground: 222.2 84% 4.9%;
343
+ --popover: 0 0% 100%;
344
+ --popover-foreground: 222.2 84% 4.9%;
345
+ --primary: 222.2 47.4% 11.2%;
346
+ --primary-foreground: 210 40% 98%;
347
+ --secondary: 210 40% 96.1%;
348
+ --secondary-foreground: 222.2 47.4% 11.2%;
349
+ --muted: 210 40% 96.1%;
350
+ --muted-foreground: 215.4 16.3% 46.9%;
351
+ --accent: 210 40% 96.1%;
352
+ --accent-foreground: 222.2 47.4% 11.2%;
353
+ --destructive: 0 84.2% 60.2%;
354
+ --destructive-foreground: 210 40% 98%;
355
+ --border: 214.3 31.8% 91.4%;
356
+ --input: 214.3 31.8% 91.4%;
357
+ --ring: 222.2 84% 4.9%;
358
+ --radius: 0.5rem;
359
+ }
360
+
361
+ .dark {
362
+ --background: 222.2 84% 4.9%;
363
+ --foreground: 210 40% 98%;
364
+ --card: 222.2 84% 4.9%;
365
+ --card-foreground: 210 40% 98%;
366
+ --popover: 222.2 84% 4.9%;
367
+ --popover-foreground: 210 40% 98%;
368
+ --primary: 210 40% 98%;
369
+ --primary-foreground: 222.2 47.4% 11.2%;
370
+ --secondary: 217.2 32.6% 17.5%;
371
+ --secondary-foreground: 210 40% 98%;
372
+ --muted: 217.2 32.6% 17.5%;
373
+ --muted-foreground: 215 20.2% 65.1%;
374
+ --accent: 217.2 32.6% 17.5%;
375
+ --accent-foreground: 210 40% 98%;
376
+ --destructive: 0 62.8% 30.6%;
377
+ --destructive-foreground: 210 40% 98%;
378
+ --border: 217.2 32.6% 17.5%;
379
+ --input: 217.2 32.6% 17.5%;
380
+ --ring: 212.7 26.8% 83.9%;
381
+ }
382
+ }
383
+
384
+ @layer base {
385
+ * {
386
+ @apply border-border;
387
+ }
388
+ body {
389
+ @apply bg-background text-foreground;
390
+ }
391
+ }
392
+ ```
393
+
394
+ ## Core Components
395
+
396
+ ### Button Component
397
+
398
+ Installation:
399
+
400
+ ```bash
401
+ npx shadcn@latest add button
402
+ ```
403
+
404
+ Basic usage:
405
+
406
+ ```tsx
407
+ import { Button } from "@/components/ui/button";
408
+
409
+ export function ButtonDemo() {
410
+ return <Button>Click me</Button>;
411
+ }
412
+ ```
413
+
414
+ Button variants:
415
+
416
+ ```tsx
417
+ import { Button } from "@/components/ui/button";
418
+
419
+ export function ButtonVariants() {
420
+ return (
421
+ <div className="flex gap-4">
422
+ <Button variant="default">Default</Button>
423
+ <Button variant="destructive">Destructive</Button>
424
+ <Button variant="outline">Outline</Button>
425
+ <Button variant="secondary">Secondary</Button>
426
+ <Button variant="ghost">Ghost</Button>
427
+ <Button variant="link">Link</Button>
428
+ </div>
429
+ );
430
+ }
431
+ ```
432
+
433
+ Button sizes:
434
+
435
+ ```tsx
436
+ <div className="flex gap-4 items-center">
437
+ <Button size="default">Default</Button>
438
+ <Button size="sm">Small</Button>
439
+ <Button size="lg">Large</Button>
440
+ <Button size="icon">
441
+ <Icon className="h-4 w-4" />
442
+ </Button>
443
+ </div>
444
+ ```
445
+
446
+ With loading state:
447
+
448
+ ```tsx
449
+ import { Button } from "@/components/ui/button";
450
+ import { Loader2 } from "lucide-react";
451
+
452
+ export function ButtonLoading() {
453
+ return (
454
+ <Button disabled>
455
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
456
+ Please wait
457
+ </Button>
458
+ );
459
+ }
460
+ ```
461
+
462
+ ### Input & Form Fields
463
+
464
+ #### Input Component
465
+
466
+ Installation:
467
+
468
+ ```bash
469
+ npx shadcn@latest add input
470
+ ```
471
+
472
+ Basic input:
473
+
474
+ ```tsx
475
+ import { Input } from "@/components/ui/input";
476
+
477
+ export function InputDemo() {
478
+ return <Input type="email" placeholder="Email" />;
479
+ }
480
+ ```
481
+
482
+ Input with label:
483
+
484
+ ```tsx
485
+ import { Input } from "@/components/ui/input";
486
+ import { Label } from "@/components/ui/label";
487
+
488
+ export function InputWithLabel() {
489
+ return (
490
+ <div className="grid w-full max-w-sm items-center gap-1.5">
491
+ <Label htmlFor="email">Email</Label>
492
+ <Input type="email" id="email" placeholder="Email" />
493
+ </div>
494
+ );
495
+ }
496
+ ```
497
+
498
+ Input with button:
499
+
500
+ ```tsx
501
+ import { Button } from "@/components/ui/button";
502
+ import { Input } from "@/components/ui/input";
503
+
504
+ export function InputWithButton() {
505
+ return (
506
+ <div className="flex w-full max-w-sm items-center gap-2">
507
+ <Input type="email" placeholder="Email" />
508
+ <Button type="submit" variant="outline">Subscribe</Button>
509
+ </div>
510
+ );
511
+ }
512
+ ```
513
+
514
+ ### Forms with Validation
515
+
516
+ Installation:
517
+
518
+ ```bash
519
+ npx shadcn@latest add form
520
+ ```
521
+
522
+ This installs React Hook Form, Zod, and form components.
523
+
524
+ Complete form example:
525
+
526
+ ```tsx
527
+ "use client"
528
+
529
+ import { zodResolver } from "@hookform/resolvers/zod"
530
+ import { useForm } from "react-hook-form"
531
+ import * as z from "zod"
532
+
533
+ import { Button } from "@/components/ui/button"
534
+ import {
535
+ Form,
536
+ FormControl,
537
+ FormDescription,
538
+ FormField,
539
+ FormItem,
540
+ FormLabel,
541
+ FormMessage,
542
+ } from "@/components/ui/form"
543
+ import { Input } from "@/components/ui/input"
544
+ import { toast } from "@/components/ui/use-toast"
545
+
546
+ const formSchema = z.object({
547
+ username: z.string().min(2, {
548
+ message: "Username must be at least 2 characters.",
549
+ }),
550
+ email: z.string().email({
551
+ message: "Please enter a valid email address.",
552
+ }),
553
+ })
554
+
555
+ export function ProfileForm() {
556
+ const form = useForm<z.infer<typeof formSchema>>({
557
+ resolver: zodResolver(formSchema),
558
+ defaultValues: {
559
+ username: "",
560
+ email: "",
561
+ },
562
+ })
563
+
564
+ function onSubmit(values: z.infer<typeof formSchema>) {
565
+ toast({
566
+ title: "You submitted the following values:",
567
+ description: (
568
+ <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
569
+ <code className="text-white">{JSON.stringify(values, null, 2)}</code>
570
+ </pre>
571
+ ),
572
+ })
573
+ }
574
+
575
+ return (
576
+ <Form {...form}>
577
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
578
+ <FormField
579
+ control={form.control}
580
+ name="username"
581
+ render={({ field }) => (
582
+ <FormItem>
583
+ <FormLabel>Username</FormLabel>
584
+ <FormControl>
585
+ <Input placeholder="shadcn" {...field} />
586
+ </FormControl>
587
+ <FormDescription>
588
+ This is your public display name.
589
+ </FormDescription>
590
+ <FormMessage />
591
+ </FormItem>
592
+ )}
593
+ />
594
+
595
+ <FormField
596
+ control={form.control}
597
+ name="email"
598
+ render={({ field }) => (
599
+ <FormItem>
600
+ <FormLabel>Email</FormLabel>
601
+ <FormControl>
602
+ <Input type="email" placeholder="you@example.com" {...field} />
603
+ </FormControl>
604
+ <FormMessage />
605
+ </FormItem>
606
+ )}
607
+ />
608
+
609
+ <Button type="submit">Submit</Button>
610
+ </form>
611
+ </Form>
612
+ )
613
+ }
614
+ ```
615
+
616
+ ### Card Component
617
+
618
+ Installation:
619
+
620
+ ```bash
621
+ npx shadcn@latest add card
622
+ ```
623
+
624
+ Basic card:
625
+
626
+ ```tsx
627
+ import {
628
+ Card,
629
+ CardContent,
630
+ CardDescription,
631
+ CardFooter,
632
+ CardHeader,
633
+ CardTitle,
634
+ } from "@/components/ui/card"
635
+
636
+ export function CardDemo() {
637
+ return (
638
+ <Card>
639
+ <CardHeader>
640
+ <CardTitle>Card Title</CardTitle>
641
+ <CardDescription>Card Description</CardDescription>
642
+ </CardHeader>
643
+ <CardContent>
644
+ <p>Card Content</p>
645
+ </CardContent>
646
+ <CardFooter>
647
+ <p>Card Footer</p>
648
+ </CardFooter>
649
+ </Card>
650
+ )
651
+ }
652
+ ```
653
+
654
+ Card with form:
655
+
656
+ ```tsx
657
+ import { Button } from "@/components/ui/button"
658
+ import {
659
+ Card,
660
+ CardContent,
661
+ CardDescription,
662
+ CardFooter,
663
+ CardHeader,
664
+ CardTitle,
665
+ } from "@/components/ui/card"
666
+ import { Input } from "@/components/ui/input"
667
+ import { Label } from "@/components/ui/label"
668
+
669
+ export function CardWithForm() {
670
+ return (
671
+ <Card className="w-[350px]">
672
+ <CardHeader>
673
+ <CardTitle>Create project</CardTitle>
674
+ <CardDescription>Deploy your new project in one-click.</CardDescription>
675
+ </CardHeader>
676
+ <CardContent>
677
+ <form>
678
+ <div className="grid w-full items-center gap-4">
679
+ <div className="flex flex-col space-y-1.5">
680
+ <Label htmlFor="name">Name</Label>
681
+ <Input id="name" placeholder="Name of your project" />
682
+ </div>
683
+ </div>
684
+ </form>
685
+ </CardContent>
686
+ <CardFooter className="flex justify-between">
687
+ <Button variant="outline">Cancel</Button>
688
+ <Button>Deploy</Button>
689
+ </CardFooter>
690
+ </Card>
691
+ )
692
+ }
693
+ ```
694
+
695
+ ### Dialog (Modal) Component
696
+
697
+ Installation:
698
+
699
+ ```bash
700
+ npx shadcn@latest add dialog
701
+ ```
702
+
703
+ Basic dialog:
704
+
705
+ ```tsx
706
+ import { Button } from "@/components/ui/button"
707
+ import {
708
+ Dialog,
709
+ DialogContent,
710
+ DialogDescription,
711
+ DialogFooter,
712
+ DialogHeader,
713
+ DialogTitle,
714
+ DialogTrigger,
715
+ } from "@/components/ui/dialog"
716
+
717
+ export function DialogDemo() {
718
+ return (
719
+ <Dialog>
720
+ <DialogTrigger asChild>
721
+ <Button variant="outline">Open Dialog</Button>
722
+ </DialogTrigger>
723
+ <DialogContent className="sm:max-w-[425px]">
724
+ <DialogHeader>
725
+ <DialogTitle>Edit profile</DialogTitle>
726
+ <DialogDescription>
727
+ Make changes to your profile here. Click save when you're done.
728
+ </DialogDescription>
729
+ </DialogHeader>
730
+ <div className="grid gap-4 py-4">
731
+ <div className="grid grid-cols-4 items-center gap-4">
732
+ <Label htmlFor="name" className="text-right">
733
+ Name
734
+ </Label>
735
+ <Input id="name" value="Pedro Duarte" className="col-span-3" />
736
+ </div>
737
+ </div>
738
+ <DialogFooter>
739
+ <Button type="submit">Save changes</Button>
740
+ </DialogFooter>
741
+ </DialogContent>
742
+ </Dialog>
743
+ )
744
+ }
745
+ ```
746
+
747
+ ### Sheet (Slide-over) Component
748
+
749
+ Installation:
750
+
751
+ ```bash
752
+ npx shadcn@latest add sheet
753
+ ```
754
+
755
+ Basic sheet:
756
+
757
+ ```tsx
758
+ import { Button } from "@/components/ui/button"
759
+ import {
760
+ Sheet,
761
+ SheetContent,
762
+ SheetDescription,
763
+ SheetHeader,
764
+ SheetTitle,
765
+ SheetTrigger,
766
+ } from "@/components/ui/sheet"
767
+
768
+ export function SheetDemo() {
769
+ return (
770
+ <Sheet>
771
+ <SheetTrigger asChild>
772
+ <Button variant="outline">Open Sheet</Button>
773
+ </SheetTrigger>
774
+ <SheetContent>
775
+ <SheetHeader>
776
+ <SheetTitle>Edit profile</SheetTitle>
777
+ <SheetDescription>
778
+ Make changes to your profile here. Click save when you're done.
779
+ </SheetDescription>
780
+ </SheetHeader>
781
+ <div className="grid gap-4 py-4">
782
+ <div className="grid grid-cols-4 items-center gap-4">
783
+ <Label htmlFor="name" className="text-right">
784
+ Name
785
+ </Label>
786
+ <Input id="name" value="Pedro Duarte" className="col-span-3" />
787
+ </div>
788
+ <div className="grid grid-cols-4 items-center gap-4">
789
+ <Label htmlFor="username" className="text-right">
790
+ Username
791
+ </Label>
792
+ <Input id="username" value="@peduarte" className="col-span-3" />
793
+ </div>
794
+ </div>
795
+ </SheetContent>
796
+ </Sheet>
797
+ )
798
+ }
799
+ ```
800
+
801
+ Sheet with side placement:
802
+
803
+ ```tsx
804
+ <Sheet>
805
+ <SheetTrigger asChild>
806
+ <Button variant="outline">Open Right Sheet</Button>
807
+ </SheetTrigger>
808
+ <SheetContent side="right">
809
+ <SheetHeader>
810
+ <SheetTitle>Settings</SheetTitle>
811
+ <SheetDescription>
812
+ Configure your application settings here.
813
+ </SheetDescription>
814
+ </SheetHeader>
815
+ {/* Settings content */}
816
+ </SheetContent>
817
+ </Sheet>
818
+ ```
819
+
820
+ ### Menubar & Navigation
821
+
822
+ #### Menubar Component
823
+
824
+ Installation:
825
+
826
+ ```bash
827
+ npx shadcn@latest add menubar
828
+ ```
829
+
830
+ Basic menubar:
831
+
832
+ ```tsx
833
+ import {
834
+ Menubar,
835
+ MenubarContent,
836
+ MenubarItem,
837
+ MenubarMenu,
838
+ MenubarSeparator,
839
+ MenubarShortcut,
840
+ MenubarSub,
841
+ MenubarSubContent,
842
+ MenubarSubTrigger,
843
+ MenubarTrigger,
844
+ } from "@/components/ui/menubar"
845
+
846
+ export function MenubarDemo() {
847
+ return (
848
+ <Menubar>
849
+ <MenubarMenu>
850
+ <MenubarTrigger>File</MenubarTrigger>
851
+ <MenubarContent>
852
+ <MenubarItem>
853
+ New Tab <MenubarShortcut>⌘T</MenubarShortcut>
854
+ </MenubarItem>
855
+ <MenubarItem>
856
+ New Window <MenubarShortcut>⌘N</MenubarShortcut>
857
+ </MenubarItem>
858
+ <MenubarSeparator />
859
+ <MenubarItem>Share</MenubarItem>
860
+ <MenubarSeparator />
861
+ <MenubarItem>Print</MenubarItem>
862
+ </MenubarContent>
863
+ </MenubarMenu>
864
+ <MenubarMenu>
865
+ <MenubarTrigger>Edit</MenubarTrigger>
866
+ <MenubarContent>
867
+ <MenubarItem>
868
+ Undo <MenubarShortcut>⌘Z</MenubarShortcut>
869
+ </MenubarItem>
870
+ <MenubarItem>
871
+ Redo <MenubarShortcut>⌘Y</MenubarShortcut>
872
+ </MenubarItem>
873
+ <MenubarSeparator />
874
+ <MenubarSub>
875
+ <MenubarSubTrigger>Find</MenubarSubTrigger>
876
+ <MenubarSubContent>
877
+ <MenubarItem>Search the web</MenubarItem>
878
+ <MenubarItem>Find...</MenubarItem>
879
+ <MenubarItem>Find Next</MenubarItem>
880
+ <MenubarItem>Find Previous</MenubarItem>
881
+ </MenubarSubContent>
882
+ </MenubarSub>
883
+ </MenubarContent>
884
+ </MenubarMenu>
885
+ </Menubar>
886
+ )
887
+ }
888
+ ```
889
+
890
+ ### Select (Dropdown) Component
891
+
892
+ Installation:
893
+
894
+ ```bash
895
+ npx shadcn@latest add select
896
+ ```
897
+
898
+ Basic select:
899
+
900
+ ```tsx
901
+ import {
902
+ Select,
903
+ SelectContent,
904
+ SelectItem,
905
+ SelectTrigger,
906
+ SelectValue,
907
+ } from "@/components/ui/select"
908
+
909
+ export function SelectDemo() {
910
+ return (
911
+ <Select>
912
+ <SelectTrigger className="w-[180px]">
913
+ <SelectValue placeholder="Select a fruit" />
914
+ </SelectTrigger>
915
+ <SelectContent>
916
+ <SelectItem value="apple">Apple</SelectItem>
917
+ <SelectItem value="banana">Banana</SelectItem>
918
+ <SelectItem value="orange">Orange</SelectItem>
919
+ </SelectContent>
920
+ </Select>
921
+ )
922
+ }
923
+ ```
924
+
925
+ Select in form:
926
+
927
+ ```tsx
928
+ <FormField
929
+ control={form.control}
930
+ name="role"
931
+ render={({ field }) => (
932
+ <FormItem>
933
+ <FormLabel>Role</FormLabel>
934
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
935
+ <FormControl>
936
+ <SelectTrigger>
937
+ <SelectValue placeholder="Select a role" />
938
+ </SelectTrigger>
939
+ </FormControl>
940
+ <SelectContent>
941
+ <SelectItem value="admin">Admin</SelectItem>
942
+ <SelectItem value="user">User</SelectItem>
943
+ <SelectItem value="guest">Guest</SelectItem>
944
+ </SelectContent>
945
+ </Select>
946
+ <FormMessage />
947
+ </FormItem>
948
+ )}
949
+ />
950
+ ```
951
+
952
+ ### Toast Notifications
953
+
954
+ Installation:
955
+
956
+ ```bash
957
+ npx shadcn@latest add toast
958
+ ```
959
+
960
+ Setup toast provider in root layout:
961
+
962
+ ```tsx
963
+ import { Toaster } from "@/components/ui/toaster"
964
+
965
+ export default function RootLayout({ children }) {
966
+ return (
967
+ <html lang="en">
968
+ <body>
969
+ {children}
970
+ <Toaster />
971
+ </body>
972
+ </html>
973
+ )
974
+ }
975
+ ```
976
+
977
+ Using toast:
978
+
979
+ ```tsx
980
+ import { useToast } from "@/components/ui/use-toast"
981
+ import { Button } from "@/components/ui/button"
982
+
983
+ export function ToastDemo() {
984
+ const { toast } = useToast()
985
+
986
+ return (
987
+ <Button
988
+ onClick={() => {
989
+ toast({
990
+ title: "Scheduled: Catch up",
991
+ description: "Friday, February 10, 2023 at 5:57 PM",
992
+ })
993
+ }}
994
+ >
995
+ Show Toast
996
+ </Button>
997
+ )
998
+ }
999
+ ```
1000
+
1001
+ Toast variants:
1002
+
1003
+ ```tsx
1004
+ // Success
1005
+ toast({
1006
+ title: "Success",
1007
+ description: "Your changes have been saved.",
1008
+ })
1009
+
1010
+ // Error
1011
+ toast({
1012
+ variant: "destructive",
1013
+ title: "Error",
1014
+ description: "Something went wrong.",
1015
+ })
1016
+
1017
+ // With action
1018
+ toast({
1019
+ title: "Uh oh! Something went wrong.",
1020
+ description: "There was a problem with your request.",
1021
+ action: <ToastAction altText="Try again">Try again</ToastAction>,
1022
+ })
1023
+ ```
1024
+
1025
+ ### Table Component
1026
+
1027
+ Installation:
1028
+
1029
+ ```bash
1030
+ npx shadcn@latest add table
1031
+ ```
1032
+
1033
+ Basic table:
1034
+
1035
+ ```tsx
1036
+ import {
1037
+ Table,
1038
+ TableBody,
1039
+ TableCaption,
1040
+ TableCell,
1041
+ TableHead,
1042
+ TableHeader,
1043
+ TableRow,
1044
+ } from "@/components/ui/table"
1045
+
1046
+ const invoices = [
1047
+ { invoice: "INV001", status: "Paid", method: "Credit Card", amount: "$250.00" },
1048
+ { invoice: "INV002", status: "Pending", method: "PayPal", amount: "$150.00" },
1049
+ ]
1050
+
1051
+ export function TableDemo() {
1052
+ return (
1053
+ <Table>
1054
+ <TableCaption>A list of your recent invoices.</TableCaption>
1055
+ <TableHeader>
1056
+ <TableRow>
1057
+ <TableHead>Invoice</TableHead>
1058
+ <TableHead>Status</TableHead>
1059
+ <TableHead>Method</TableHead>
1060
+ <TableHead className="text-right">Amount</TableHead>
1061
+ </TableRow>
1062
+ </TableHeader>
1063
+ <TableBody>
1064
+ {invoices.map((invoice) => (
1065
+ <TableRow key={invoice.invoice}>
1066
+ <TableCell className="font-medium">{invoice.invoice}</TableCell>
1067
+ <TableCell>{invoice.status}</TableCell>
1068
+ <TableCell>{invoice.method}</TableCell>
1069
+ <TableCell className="text-right">{invoice.amount}</TableCell>
1070
+ </TableRow>
1071
+ ))}
1072
+ </TableBody>
1073
+ </Table>
1074
+ )
1075
+ }
1076
+ ```
1077
+
1078
+ ### Charts Component
1079
+
1080
+ Installation:
1081
+
1082
+ ```bash
1083
+ npx shadcn@latest add chart
1084
+ ```
1085
+
1086
+ The charts component in shadcn/ui is built on **Recharts** - providing direct access to all Recharts capabilities with consistent theming and styling.
1087
+
1088
+ #### ChartContainer and ChartConfig
1089
+
1090
+ The `ChartContainer` wraps your Recharts component and accepts a `config` prop for theming:
1091
+
1092
+ ```tsx
1093
+ import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
1094
+ import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
1095
+
1096
+ const chartConfig = {
1097
+ desktop: {
1098
+ label: "Desktop",
1099
+ color: "var(--chart-1)",
1100
+ },
1101
+ mobile: {
1102
+ label: "Mobile",
1103
+ color: "var(--chart-2)",
1104
+ },
1105
+ } satisfies import("@/components/ui/chart").ChartConfig
1106
+
1107
+ const chartData = [
1108
+ { month: "January", desktop: 186, mobile: 80 },
1109
+ { month: "February", desktop: 305, mobile: 200 },
1110
+ { month: "March", desktop: 237, mobile: 120 },
1111
+ ]
1112
+
1113
+ export function BarChartDemo() {
1114
+ return (
1115
+ <ChartContainer config={chartConfig} className="min-h-[200px] w-full">
1116
+ <BarChart data={chartData}>
1117
+ <CartesianGrid vertical={false} />
1118
+ <XAxis
1119
+ dataKey="month"
1120
+ tickLine={false}
1121
+ axisLine={false}
1122
+ tickFormatter={(value) => value.slice(0, 3)}
1123
+ />
1124
+ <Bar
1125
+ dataKey="desktop"
1126
+ fill="var(--color-desktop)"
1127
+ radius={4}
1128
+ />
1129
+ <Bar
1130
+ dataKey="mobile"
1131
+ fill="var(--color-mobile)"
1132
+ radius={4}
1133
+ />
1134
+ <ChartTooltip content={<ChartTooltipContent />} />
1135
+ </BarChart>
1136
+ </ChartContainer>
1137
+ )
1138
+ }
1139
+ ```
1140
+
1141
+ #### ChartConfig with Custom Colors
1142
+
1143
+ You can define custom colors directly in the configuration:
1144
+
1145
+ ```tsx
1146
+ const chartConfig = {
1147
+ visitors: {
1148
+ label: "Visitors",
1149
+ color: "#2563eb", // Custom hex color
1150
+ theme: {
1151
+ light: "#2563eb",
1152
+ dark: "#60a5fa",
1153
+ },
1154
+ },
1155
+ sales: {
1156
+ label: "Sales",
1157
+ color: "var(--chart-1)", // CSS variable
1158
+ theme: {
1159
+ light: "oklch(0.646 0.222 41.116)",
1160
+ dark: "oklch(0.696 0.182 281.41)",
1161
+ },
1162
+ },
1163
+ } satisfies import("@/components/ui/chart").ChartConfig
1164
+ ```
1165
+
1166
+ #### CSS Variables for Charts
1167
+
1168
+ Add chart color variables to your `globals.css`:
1169
+
1170
+ ```css
1171
+ @layer base {
1172
+ :root {
1173
+ /* Chart colors */
1174
+ --chart-1: oklch(0.646 0.222 41.116);
1175
+ --chart-2: oklch(0.6 0.118 184.704);
1176
+ --chart-3: oklch(0.546 0.198 38.228);
1177
+ --chart-4: oklch(0.596 0.151 343.253);
1178
+ --chart-5: oklch(0.546 0.158 49.157);
1179
+ }
1180
+
1181
+ .dark {
1182
+ --chart-1: oklch(0.488 0.243 264.376);
1183
+ --chart-2: oklch(0.696 0.17 162.48);
1184
+ --chart-3: oklch(0.698 0.141 24.311);
1185
+ --chart-4: oklch(0.676 0.172 171.196);
1186
+ --chart-5: oklch(0.578 0.192 302.85);
1187
+ }
1188
+ }
1189
+ ```
1190
+
1191
+ #### Line Chart Example
1192
+
1193
+ ```tsx
1194
+ import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts"
1195
+ import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
1196
+
1197
+ const chartConfig = {
1198
+ price: {
1199
+ label: "Price",
1200
+ color: "var(--chart-1)",
1201
+ },
1202
+ } satisfies import("@/components/ui/chart").ChartConfig
1203
+
1204
+ const chartData = [
1205
+ { month: "January", price: 186 },
1206
+ { month: "February", price: 305 },
1207
+ { month: "March", price: 237 },
1208
+ { month: "April", price: 203 },
1209
+ { month: "May", price: 276 },
1210
+ ]
1211
+
1212
+ export function LineChartDemo() {
1213
+ return (
1214
+ <ChartContainer config={chartConfig} className="min-h-[200px]">
1215
+ <LineChart data={chartData}>
1216
+ <CartesianGrid vertical={false} />
1217
+ <XAxis dataKey="month" tickLine={false} axisLine={false} />
1218
+ <YAxis tickLine={false} axisLine={false} tickFormatter={(value) => `$${value}`} />
1219
+ <Line
1220
+ dataKey="price"
1221
+ stroke="var(--color-price)"
1222
+ strokeWidth={2}
1223
+ dot={false}
1224
+ />
1225
+ <ChartTooltip content={<ChartTooltipContent />} />
1226
+ </LineChart>
1227
+ </ChartContainer>
1228
+ )
1229
+ }
1230
+ ```
1231
+
1232
+ #### Area Chart Example
1233
+
1234
+ ```tsx
1235
+ import { Area, AreaChart, XAxis, YAxis } from "recharts"
1236
+ import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltipContent } from "@/components/ui/chart"
1237
+
1238
+ const chartConfig = {
1239
+ desktop: { label: "Desktop", color: "var(--chart-1)" },
1240
+ mobile: { label: "Mobile", color: "var(--chart-2)" },
1241
+ } satisfies import("@/components/ui/chart").ChartConfig
1242
+
1243
+ export function AreaChartDemo() {
1244
+ return (
1245
+ <ChartContainer config={chartConfig} className="min-h-[200px]">
1246
+ <AreaChart data={chartData}>
1247
+ <XAxis dataKey="month" tickLine={false} axisLine={false} />
1248
+ <YAxis tickLine={false} axisLine={false} />
1249
+ <Area
1250
+ dataKey="desktop"
1251
+ fill="var(--color-desktop)"
1252
+ stroke="var(--color-desktop)"
1253
+ fillOpacity={0.3}
1254
+ />
1255
+ <Area
1256
+ dataKey="mobile"
1257
+ fill="var(--color-mobile)"
1258
+ stroke="var(--color-mobile)"
1259
+ fillOpacity={0.3}
1260
+ />
1261
+ <ChartTooltip content={<ChartTooltipContent />} />
1262
+ <ChartLegend content={<ChartLegendContent />} />
1263
+ </AreaChart>
1264
+ </ChartContainer>
1265
+ )
1266
+ }
1267
+ ```
1268
+
1269
+ #### Pie Chart Example
1270
+
1271
+ ```tsx
1272
+ import { Pie, PieChart } from "recharts"
1273
+ import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltipContent } from "@/components/ui/chart"
1274
+
1275
+ const chartConfig = {
1276
+ chrome: { label: "Chrome", color: "var(--chart-1)" },
1277
+ safari: { label: "Safari", color: "var(--chart-2)" },
1278
+ firefox: { label: "Firefox", color: "var(--chart-3)" },
1279
+ } satisfies import("@/components/ui/chart").ChartConfig
1280
+
1281
+ const pieData = [
1282
+ { browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
1283
+ { browser: "Safari", visitors: 200, fill: "var(--color-safari)" },
1284
+ { browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
1285
+ ]
1286
+
1287
+ export function PieChartDemo() {
1288
+ return (
1289
+ <ChartContainer config={chartConfig} className="min-h-[200px]">
1290
+ <PieChart>
1291
+ <Pie
1292
+ data={pieData}
1293
+ dataKey="visitors"
1294
+ nameKey="browser"
1295
+ cx="50%"
1296
+ cy="50%"
1297
+ outerRadius={80}
1298
+ />
1299
+ <ChartTooltip content={<ChartTooltipContent />} />
1300
+ <ChartLegend content={<ChartLegendContent />} />
1301
+ </PieChart>
1302
+ </ChartContainer>
1303
+ )
1304
+ }
1305
+ ```
1306
+
1307
+ #### ChartTooltipContent Props
1308
+
1309
+ | Prop | Type | Default | Description |
1310
+ |------|------|---------|-------------|
1311
+ | `labelKey` | string | "label" | Key for tooltip label |
1312
+ | `nameKey` | string | "name" | Key for tooltip name |
1313
+ | `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style |
1314
+ | `hideLabel` | boolean | false | Hide label |
1315
+ | `hideIndicator` | boolean | false | Hide indicator |
1316
+
1317
+ #### Accessibility
1318
+
1319
+ Enable keyboard navigation and screen reader support:
1320
+
1321
+ ```tsx
1322
+ <BarChart accessibilityLayer data={chartData}>...</BarChart>
1323
+ ```
1324
+
1325
+ This adds:
1326
+ - Keyboard arrow key navigation
1327
+ - ARIA labels for chart elements
1328
+ - Screen reader announcements for data values
1329
+
1330
+ ## Customization
1331
+
1332
+ ### Theming with CSS Variables
1333
+
1334
+ shadcn/ui uses CSS variables for theming. Configure in `globals.css`:
1335
+
1336
+ ```css
1337
+ @layer base {
1338
+ :root {
1339
+ --background: 0 0% 100%;
1340
+ --foreground: 222.2 84% 4.9%;
1341
+ --primary: 222.2 47.4% 11.2%;
1342
+ --primary-foreground: 210 40% 98%;
1343
+ --secondary: 210 40% 96.1%;
1344
+ --secondary-foreground: 222.2 47.4% 11.2%;
1345
+ --muted: 210 40% 96.1%;
1346
+ --muted-foreground: 215.4 16.3% 46.9%;
1347
+ --accent: 210 40% 96.1%;
1348
+ --accent-foreground: 222.2 47.4% 11.2%;
1349
+ --destructive: 0 84.2% 60.2%;
1350
+ --destructive-foreground: 210 40% 98%;
1351
+ --border: 214.3 31.8% 91.4%;
1352
+ --input: 214.3 31.8% 91.4%;
1353
+ --ring: 222.2 84% 4.9%;
1354
+ --radius: 0.5rem;
1355
+ }
1356
+
1357
+ .dark {
1358
+ --background: 222.2 84% 4.9%;
1359
+ --foreground: 210 40% 98%;
1360
+ --primary: 210 40% 98%;
1361
+ --primary-foreground: 222.2 47.4% 11.2%;
1362
+ /* ... other dark mode variables */
1363
+ }
1364
+ }
1365
+ ```
1366
+
1367
+ ### Customizing Components
1368
+
1369
+ Since you own the code, customize directly:
1370
+
1371
+ ```tsx
1372
+ // components/ui/button.tsx
1373
+ import * as React from "react"
1374
+ import { Slot } from "@radix-ui/react-slot"
1375
+ import { cva, type VariantProps } from "class-variance-authority"
1376
+ import { cn } from "@/lib/utils"
1377
+
1378
+ const buttonVariants = cva(
1379
+ "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
1380
+ {
1381
+ variants: {
1382
+ variant: {
1383
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
1384
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
1385
+ outline: "border border-input bg-background hover:bg-accent",
1386
+ // Add custom variant
1387
+ custom: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
1388
+ },
1389
+ size: {
1390
+ default: "h-10 px-4 py-2",
1391
+ sm: "h-9 rounded-md px-3",
1392
+ lg: "h-11 rounded-md px-8",
1393
+ // Add custom size
1394
+ xl: "h-14 rounded-md px-10 text-lg",
1395
+ },
1396
+ },
1397
+ defaultVariants: {
1398
+ variant: "default",
1399
+ size: "default",
1400
+ },
1401
+ }
1402
+ )
1403
+
1404
+ export interface ButtonProps
1405
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
1406
+ VariantProps<typeof buttonVariants> {
1407
+ asChild?: boolean
1408
+ }
1409
+
1410
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
1411
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
1412
+ const Comp = asChild ? Slot : "button"
1413
+ return (
1414
+ <Comp
1415
+ className={cn(buttonVariants({ variant, size, className }))}
1416
+ ref={ref}
1417
+ {...props}
1418
+ />
1419
+ )
1420
+ }
1421
+ )
1422
+ Button.displayName = "Button"
1423
+
1424
+ export { Button, buttonVariants }
1425
+ ```
1426
+
1427
+ ## Next.js Integration
1428
+
1429
+ ### App Router Setup
1430
+
1431
+ For Next.js 13+ with App Router, ensure components use `"use client"` directive:
1432
+
1433
+ ```tsx
1434
+ // src/components/ui/button.tsx
1435
+ "use client"
1436
+
1437
+ import * as React from "react"
1438
+ import { Slot } from "@radix-ui/react-slot"
1439
+ import { cva, type VariantProps } from "class-variance-authority"
1440
+ import { cn } from "@/lib/utils"
1441
+
1442
+ // ... rest of component
1443
+ ```
1444
+
1445
+ ### Layout Integration
1446
+
1447
+ Add the Toaster to your root layout:
1448
+
1449
+ ```tsx
1450
+ // app/layout.tsx
1451
+ import { Toaster } from "@/components/ui/toaster"
1452
+ import "./globals.css"
1453
+
1454
+ export default function RootLayout({
1455
+ children,
1456
+ }: {
1457
+ children: React.ReactNode
1458
+ }) {
1459
+ return (
1460
+ <html lang="en" suppressHydrationWarning>
1461
+ <body className="min-h-screen bg-background font-sans antialiased">
1462
+ {children}
1463
+ <Toaster />
1464
+ </body>
1465
+ </html>
1466
+ )
1467
+ }
1468
+ ```
1469
+
1470
+ ### Server Components
1471
+
1472
+ When using shadcn/ui components in Server Components, wrap them in a Client Component:
1473
+
1474
+ ```tsx
1475
+ // app/dashboard/page.tsx
1476
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
1477
+ import { ButtonClient } from "@/components/ui/button-client"
1478
+
1479
+ export default function DashboardPage() {
1480
+ return (
1481
+ <div className="container mx-auto p-6">
1482
+ <Card>
1483
+ <CardHeader>
1484
+ <CardTitle>Dashboard</CardTitle>
1485
+ </CardHeader>
1486
+ <CardContent>
1487
+ <ButtonClient>Interactive Button</ButtonClient>
1488
+ </CardContent>
1489
+ </Card>
1490
+ </div>
1491
+ )
1492
+ }
1493
+ ```
1494
+
1495
+ ```tsx
1496
+ // src/components/ui/button-client.tsx
1497
+ "use client"
1498
+
1499
+ import { Button } from "./button"
1500
+
1501
+ export function ButtonClient(props: React.ComponentProps<typeof Button>) {
1502
+ return <Button {...props} />
1503
+ }
1504
+ ```
1505
+
1506
+ ### Route Handlers with Forms
1507
+
1508
+ Create API routes for form submissions:
1509
+
1510
+ ```tsx
1511
+ // app/api/contact/route.ts
1512
+ import { NextRequest, NextResponse } from "next/server"
1513
+ import { z } from "zod"
1514
+
1515
+ const contactSchema = z.object({
1516
+ name: z.string().min(2),
1517
+ email: z.string().email(),
1518
+ message: z.string().min(10),
1519
+ })
1520
+
1521
+ export async function POST(request: NextRequest) {
1522
+ try {
1523
+ const body = await request.json()
1524
+ const validated = contactSchema.parse(body)
1525
+
1526
+ // Process form data
1527
+ console.log("Form submission:", validated)
1528
+
1529
+ return NextResponse.json({ success: true })
1530
+ } catch (error) {
1531
+ if (error instanceof z.ZodError) {
1532
+ return NextResponse.json(
1533
+ { errors: error.errors },
1534
+ { status: 400 }
1535
+ )
1536
+ }
1537
+
1538
+ return NextResponse.json(
1539
+ { error: "Internal server error" },
1540
+ { status: 500 }
1541
+ )
1542
+ }
1543
+ }
1544
+ ```
1545
+
1546
+ ### Form with Server Action
1547
+
1548
+ Using Next.js 14+ Server Actions:
1549
+
1550
+ ```tsx
1551
+ // app/contact/page.tsx
1552
+ "use client"
1553
+
1554
+ import { zodResolver } from "@hookform/resolvers/zod"
1555
+ import { useForm } from "react-hook-form"
1556
+ import * as z from "zod"
1557
+ import { Button } from "@/components/ui/button"
1558
+ import {
1559
+ Form,
1560
+ FormControl,
1561
+ FormField,
1562
+ FormItem,
1563
+ FormLabel,
1564
+ FormMessage,
1565
+ } from "@/components/ui/form"
1566
+ import { Input } from "@/components/ui/input"
1567
+ import { Textarea } from "@/components/ui/textarea"
1568
+ import { toast } from "@/components/ui/use-toast"
1569
+
1570
+ const formSchema = z.object({
1571
+ name: z.string().min(2),
1572
+ email: z.string().email(),
1573
+ message: z.string().min(10),
1574
+ })
1575
+
1576
+ async function onSubmit(values: z.infer<typeof formSchema>) {
1577
+ try {
1578
+ const response = await fetch("/api/contact", {
1579
+ method: "POST",
1580
+ headers: { "Content-Type": "application/json" },
1581
+ body: JSON.stringify(values),
1582
+ })
1583
+
1584
+ if (!response.ok) throw new Error("Failed to submit")
1585
+
1586
+ toast({
1587
+ title: "Success!",
1588
+ description: "Your message has been sent.",
1589
+ })
1590
+ } catch (error) {
1591
+ toast({
1592
+ variant: "destructive",
1593
+ title: "Error",
1594
+ description: "Failed to send message. Please try again.",
1595
+ })
1596
+ }
1597
+ }
1598
+
1599
+ export default function ContactPage() {
1600
+ const form = useForm<z.infer<typeof formSchema>>({
1601
+ resolver: zodResolver(formSchema),
1602
+ })
1603
+
1604
+ return (
1605
+ <div className="container mx-auto max-w-2xl py-8">
1606
+ <Form {...form}>
1607
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
1608
+ <FormField
1609
+ control={form.control}
1610
+ name="name"
1611
+ render={({ field }) => (
1612
+ <FormItem>
1613
+ <FormLabel>Name</FormLabel>
1614
+ <FormControl>
1615
+ <Input placeholder="Your name" {...field} />
1616
+ </FormControl>
1617
+ <FormMessage />
1618
+ </FormItem>
1619
+ )}
1620
+ />
1621
+
1622
+ <FormField
1623
+ control={form.control}
1624
+ name="email"
1625
+ render={({ field }) => (
1626
+ <FormItem>
1627
+ <FormLabel>Email</FormLabel>
1628
+ <FormControl>
1629
+ <Input type="email" placeholder="your@email.com" {...field} />
1630
+ </FormControl>
1631
+ <FormMessage />
1632
+ </FormItem>
1633
+ )}
1634
+ />
1635
+
1636
+ <FormField
1637
+ control={form.control}
1638
+ name="message"
1639
+ render={({ field }) => (
1640
+ <FormItem>
1641
+ <FormLabel>Message</FormLabel>
1642
+ <FormControl>
1643
+ <Textarea
1644
+ placeholder="Your message..."
1645
+ className="resize-none"
1646
+ {...field}
1647
+ />
1648
+ </FormControl>
1649
+ <FormMessage />
1650
+ </FormItem>
1651
+ )}
1652
+ />
1653
+
1654
+ <Button type="submit" className="w-full">
1655
+ Send Message
1656
+ </Button>
1657
+ </form>
1658
+ </Form>
1659
+ </div>
1660
+ )
1661
+ }
1662
+ ```
1663
+
1664
+ ### Metadata with shadcn/ui
1665
+
1666
+ Using shadcn/ui components in metadata:
1667
+
1668
+ ```tsx
1669
+ // app/layout.tsx
1670
+ import { Metadata } from "next"
1671
+
1672
+ export const metadata: Metadata = {
1673
+ title: {
1674
+ default: "My App",
1675
+ template: "%s | My App",
1676
+ },
1677
+ description: "Built with shadcn/ui and Next.js",
1678
+ }
1679
+
1680
+ // app/about/page.tsx
1681
+ import { Metadata } from "next"
1682
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
1683
+
1684
+ export const metadata: Metadata = {
1685
+ title: "About Us",
1686
+ description: "Learn more about our company",
1687
+ }
1688
+
1689
+ export default function AboutPage() {
1690
+ return (
1691
+ <div className="container mx-auto py-8">
1692
+ <Card>
1693
+ <CardHeader>
1694
+ <CardTitle>About Our Company</CardTitle>
1695
+ </CardHeader>
1696
+ <CardContent>
1697
+ <p>We build amazing products with modern web technologies.</p>
1698
+ </CardContent>
1699
+ </Card>
1700
+ </div>
1701
+ )
1702
+ }
1703
+ ```
1704
+
1705
+ ### Font Optimization
1706
+
1707
+ Optimize fonts with next/font:
1708
+
1709
+ ```tsx
1710
+ // app/layout.tsx
1711
+ import { Inter } from "next/font/google"
1712
+ import { Toaster } from "@/components/ui/toaster"
1713
+ import { cn } from "@/lib/utils"
1714
+ import "./globals.css"
1715
+
1716
+ const inter = Inter({ subsets: ["latin"] })
1717
+
1718
+ export default function RootLayout({
1719
+ children,
1720
+ }: {
1721
+ children: React.ReactNode
1722
+ }) {
1723
+ return (
1724
+ <html lang="en" suppressHydrationWarning>
1725
+ <body className={cn("min-h-screen bg-background font-sans antialiased", inter.className)}>
1726
+ {children}
1727
+ <Toaster />
1728
+ </body>
1729
+ </html>
1730
+ )
1731
+ }
1732
+ ```
1733
+
1734
+ ## Advanced Patterns
1735
+
1736
+ ### Form with Multiple Fields
1737
+
1738
+ ```tsx
1739
+ const formSchema = z.object({
1740
+ username: z.string().min(2).max(50),
1741
+ email: z.string().email(),
1742
+ bio: z.string().max(160).min(4),
1743
+ role: z.enum(["admin", "user", "guest"]),
1744
+ notifications: z.boolean().default(false),
1745
+ })
1746
+
1747
+ export function AdvancedForm() {
1748
+ const form = useForm<z.infer<typeof formSchema>>({
1749
+ resolver: zodResolver(formSchema),
1750
+ defaultValues: {
1751
+ username: "",
1752
+ email: "",
1753
+ bio: "",
1754
+ role: "user",
1755
+ notifications: false,
1756
+ },
1757
+ })
1758
+
1759
+ function onSubmit(values: z.infer<typeof formSchema>) {
1760
+ console.log(values)
1761
+ }
1762
+
1763
+ return (
1764
+ <Form {...form}>
1765
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
1766
+ {/* Username field */}
1767
+ <FormField
1768
+ control={form.control}
1769
+ name="username"
1770
+ render={({ field }) => (
1771
+ <FormItem>
1772
+ <FormLabel>Username</FormLabel>
1773
+ <FormControl>
1774
+ <Input placeholder="johndoe" {...field} />
1775
+ </FormControl>
1776
+ <FormMessage />
1777
+ </FormItem>
1778
+ )}
1779
+ />
1780
+
1781
+ {/* Email field */}
1782
+ <FormField
1783
+ control={form.control}
1784
+ name="email"
1785
+ render={({ field }) => (
1786
+ <FormItem>
1787
+ <FormLabel>Email</FormLabel>
1788
+ <FormControl>
1789
+ <Input type="email" placeholder="john@example.com" {...field} />
1790
+ </FormControl>
1791
+ <FormMessage />
1792
+ </FormItem>
1793
+ )}
1794
+ />
1795
+
1796
+ {/* Textarea field */}
1797
+ <FormField
1798
+ control={form.control}
1799
+ name="bio"
1800
+ render={({ field }) => (
1801
+ <FormItem>
1802
+ <FormLabel>Bio</FormLabel>
1803
+ <FormControl>
1804
+ <Textarea
1805
+ placeholder="Tell us about yourself"
1806
+ className="resize-none"
1807
+ {...field}
1808
+ />
1809
+ </FormControl>
1810
+ <FormMessage />
1811
+ </FormItem>
1812
+ )}
1813
+ />
1814
+
1815
+ {/* Select field */}
1816
+ <FormField
1817
+ control={form.control}
1818
+ name="role"
1819
+ render={({ field }) => (
1820
+ <FormItem>
1821
+ <FormLabel>Role</FormLabel>
1822
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
1823
+ <FormControl>
1824
+ <SelectTrigger>
1825
+ <SelectValue placeholder="Select a role" />
1826
+ </SelectTrigger>
1827
+ </FormControl>
1828
+ <SelectContent>
1829
+ <SelectItem value="admin">Admin</SelectItem>
1830
+ <SelectItem value="user">User</SelectItem>
1831
+ <SelectItem value="guest">Guest</SelectItem>
1832
+ </SelectContent>
1833
+ </Select>
1834
+ <FormMessage />
1835
+ </FormItem>
1836
+ )}
1837
+ />
1838
+
1839
+ {/* Checkbox field */}
1840
+ <FormField
1841
+ control={form.control}
1842
+ name="notifications"
1843
+ render={({ field }) => (
1844
+ <FormItem className="flex flex-row items-start space-x-3 space-y-0">
1845
+ <FormControl>
1846
+ <Checkbox
1847
+ checked={field.value}
1848
+ onCheckedChange={field.onChange}
1849
+ />
1850
+ </FormControl>
1851
+ <div className="space-y-1 leading-none">
1852
+ <FormLabel>Email notifications</FormLabel>
1853
+ <FormDescription>
1854
+ Receive emails about your account activity.
1855
+ </FormDescription>
1856
+ </div>
1857
+ </FormItem>
1858
+ )}
1859
+ />
1860
+
1861
+ <Button type="submit">Submit</Button>
1862
+ </form>
1863
+ </Form>
1864
+ )
1865
+ }
1866
+ ```
1867
+
1868
+ ## Best Practices
1869
+
1870
+ 1. **Accessibility**: Components use Radix UI primitives for ARIA compliance
1871
+ 2. **Customization**: Modify components directly in your codebase
1872
+ 3. **Type Safety**: Use TypeScript for type-safe props and state
1873
+ 4. **Validation**: Use Zod schemas for form validation
1874
+ 5. **Styling**: Leverage Tailwind utilities and CSS variables
1875
+ 6. **Consistency**: Use the same component patterns across your app
1876
+ 7. **Testing**: Components are testable with React Testing Library
1877
+ 8. **Performance**: Components are optimized and tree-shakeable
1878
+
1879
+ ## Common Component Combinations
1880
+
1881
+ ### Login Form
1882
+
1883
+ ```tsx
1884
+ <Card className="w-[350px]">
1885
+ <CardHeader>
1886
+ <CardTitle>Login</CardTitle>
1887
+ <CardDescription>Enter your credentials to continue</CardDescription>
1888
+ </CardHeader>
1889
+ <CardContent>
1890
+ <Form {...form}>
1891
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
1892
+ <FormField
1893
+ control={form.control}
1894
+ name="email"
1895
+ render={({ field }) => (
1896
+ <FormItem>
1897
+ <FormLabel>Email</FormLabel>
1898
+ <FormControl>
1899
+ <Input type="email" placeholder="you@example.com" {...field} />
1900
+ </FormControl>
1901
+ <FormMessage />
1902
+ </FormItem>
1903
+ )}
1904
+ />
1905
+ <FormField
1906
+ control={form.control}
1907
+ name="password"
1908
+ render={({ field }) => (
1909
+ <FormItem>
1910
+ <FormLabel>Password</FormLabel>
1911
+ <FormControl>
1912
+ <Input type="password" {...field} />
1913
+ </FormControl>
1914
+ <FormMessage />
1915
+ </FormItem>
1916
+ )}
1917
+ />
1918
+ <Button type="submit" className="w-full">Login</Button>
1919
+ </form>
1920
+ </Form>
1921
+ </CardContent>
1922
+ </Card>
1923
+ ```
1924
+
1925
+ ## References
1926
+
1927
+ - Official Docs: https://ui.shadcn.com
1928
+ - Radix UI: https://www.radix-ui.com
1929
+ - React Hook Form: https://react-hook-form.com
1930
+ - Zod: https://zod.dev
1931
+ - Tailwind CSS: https://tailwindcss.com
1932
+ - Examples: https://ui.shadcn.com/examples