diff --git a/.agent/skills/shadcn-ui b/.agent/skills/shadcn-ui deleted file mode 120000 index 3223608..0000000 --- a/.agent/skills/shadcn-ui +++ /dev/null @@ -1 +0,0 @@ -/mnt/c/Users/Zenchant/codex/beadboard/.agents/skills/shadcn-ui/ \ No newline at end of file diff --git a/.agent/skills/shadcn-ui/SKILL.md b/.agent/skills/shadcn-ui/SKILL.md new file mode 100644 index 0000000..2ba3e29 --- /dev/null +++ b/.agent/skills/shadcn-ui/SKILL.md @@ -0,0 +1,1930 @@ +--- +name: shadcn-ui +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. +allowed-tools: Read, Write, Bash, Edit, Glob +--- + +# shadcn/ui Component Patterns + +## Overview + +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. + +## Table of Contents + +- [When to Use](#when-to-use) +- [Quick Start](#quick-start) +- [Installation & Setup](#installation--setup) +- [Project Configuration](#project-configuration) +- [Core Components](#core-components) + - [Button](#button-component) + - [Input & Form Fields](#input--form-fields) + - [Forms with Validation](#forms-with-validation) + - [Card](#card-component) + - [Dialog (Modal)](#dialog-modal-component) + - [Select (Dropdown)](#select-dropdown-component) + - [Sheet (Slide-over)](#sheet-slide-over-component) + - [Menubar & Navigation](#menubar--navigation) + - [Table](#table-component) + - [Toast Notifications](#toast-notifications) + - [Charts](#charts-component) +- [Advanced Patterns](#advanced-patterns) +- [Customization](#customization) +- [Next.js Integration](#nextjs-integration) +- [Best Practices](#best-practices) +- [Common Component Combinations](#common-component-combinations) + +## When to Use + +- 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 +- Creating complex layouts and data displays + +## Instructions + +1. **Initialize Project**: Run `npx shadcn@latest init` to configure shadcn/ui +2. **Install Components**: Add components with `npx shadcn@latest add ` +3. **Configure Theme**: Customize CSS variables in globals.css for theming +4. **Import Components**: Use components from `@/components/ui/` directory +5. **Customize as Needed**: Modify component code directly in your project +6. **Add Form Validation**: Integrate React Hook Form with Zod schemas +7. **Test Accessibility**: Verify ARIA attributes and keyboard navigation + +## Examples + +### Complete Form with Validation + +```tsx +"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>({ + resolver: zodResolver(formSchema), + defaultValues: { email: "", password: "" }, + }) + + return ( +
+ + ( + + Email + + + + )} /> + + + + ) +} +``` + +## Constraints and Warnings + +- **Not an NPM Package**: Components are copied to your project; you own the code +- **Client Components**: Most components require "use client" directive +- **Radix Dependencies**: Ensure all @radix-ui packages are installed +- **Tailwind Required**: Components rely on Tailwind CSS utilities +- **TypeScript**: Designed for TypeScript projects; type definitions included +- **Path Aliases**: Configure @ alias in tsconfig.json for imports +- **Dark Mode**: Set up dark mode with CSS variables or class strategy + +## Quick Start + +For new projects, use the automated setup: + +```bash +# Create Next.js project with shadcn/ui +npx create-next-app@latest my-app --typescript --tailwind --eslint --app +cd my-app +npx shadcn@latest init + +# Install essential components +npx shadcn@latest add button input form card dialog select +``` + +For existing projects: + +```bash +# Install dependencies +npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react + +# Initialize shadcn/ui +npx shadcn@latest init +``` + +## What is shadcn/ui? + +shadcn/ui is **not** a traditional component library or npm package. Instead: + +- It's a **collection of reusable components** that you can copy into your project +- Components are **yours to customize** - you own the code +- Built with **Radix UI** primitives for accessibility +- Styled with **Tailwind CSS** utilities +- Includes CLI tool for easy component installation + +## Installation & Setup + +### Initial Setup + +```bash +# Initialize shadcn/ui in your project +npx shadcn@latest init +``` + +During setup, you'll configure: +- TypeScript or JavaScript +- Style (Default, New York, etc.) +- Base color theme +- CSS variables or Tailwind CSS classes +- Component installation path + +### Installing Individual Components + +```bash +# Install a single component +npx shadcn@latest add button + +# Install multiple components +npx shadcn@latest add button input form + +# Install all components +npx shadcn@latest add --all +``` + +### Manual Installation + +If you prefer manual setup: + +```bash +# Install dependencies for a specific component +npm install @radix-ui/react-slot + +# Copy component code from ui.shadcn.com +# Place in src/components/ui/ +``` + +## Project Configuration + +### Required Dependencies + +```json +{ + "dependencies": { + "@radix-ui/react-accordion": "^1.1.2", + "@radix-ui/react-alert-dialog": "^1.0.5", + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-separator": "^1.0.3", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-toast": "^1.1.5", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", + "lucide-react": "^0.294.0", + "tailwind-merge": "^2.0.0", + "tailwindcss-animate": "^1.0.7" + } +} +``` + +### TSConfig Configuration + +```json +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "es6"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "baseUrl": ".", + "paths": { + "@/components/*": ["./src/components/*"], + "@/lib/*": ["./src/lib/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} +``` + +### Tailwind Configuration + +```js +// tailwind.config.js +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: [ + './pages/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './src/**/*.{ts,tsx}', + ], + prefix: "", + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} +``` + +### CSS Variables (globals.css) + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 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%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} +``` + +## Core Components + +### Button Component + +Installation: + +```bash +npx shadcn@latest add button +``` + +Basic usage: + +```tsx +import { Button } from "@/components/ui/button"; + +export function ButtonDemo() { + return ; +} +``` + +Button variants: + +```tsx +import { Button } from "@/components/ui/button"; + +export function ButtonVariants() { + return ( +
+ + + + + + +
+ ); +} +``` + +Button sizes: + +```tsx +
+ + + + +
+``` + +With loading state: + +```tsx +import { Button } from "@/components/ui/button"; +import { Loader2 } from "lucide-react"; + +export function ButtonLoading() { + return ( + + ); +} +``` + +### Input & Form Fields + +#### Input Component + +Installation: + +```bash +npx shadcn@latest add input +``` + +Basic input: + +```tsx +import { Input } from "@/components/ui/input"; + +export function InputDemo() { + return ; +} +``` + +Input with label: + +```tsx +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; + +export function InputWithLabel() { + return ( +
+ + +
+ ); +} +``` + +Input with button: + +```tsx +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; + +export function InputWithButton() { + return ( +
+ + +
+ ); +} +``` + +### Forms with Validation + +Installation: + +```bash +npx shadcn@latest add form +``` + +This installs React Hook Form, Zod, and form components. + +Complete form example: + +```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 { toast } from "@/components/ui/use-toast" + +const formSchema = z.object({ + username: z.string().min(2, { + message: "Username must be at least 2 characters.", + }), + email: z.string().email({ + message: "Please enter a valid email address.", + }), +}) + +export function ProfileForm() { + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + username: "", + email: "", + }, + }) + + function onSubmit(values: z.infer) { + toast({ + title: "You submitted the following values:", + description: ( +
+          {JSON.stringify(values, null, 2)}
+        
+ ), + }) + } + + return ( +
+ + ( + + Username + + + + + This is your public display name. + + + + )} + /> + + ( + + Email + + + + + + )} + /> + + + + + ) +} +``` + +### Card Component + +Installation: + +```bash +npx shadcn@latest add card +``` + +Basic card: + +```tsx +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" + +export function CardDemo() { + return ( + + + Card Title + Card Description + + +

Card Content

+
+ +

Card Footer

+
+
+ ) +} +``` + +Card with form: + +```tsx +import { Button } from "@/components/ui/button" +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +export function CardWithForm() { + return ( + + + Create project + Deploy your new project in one-click. + + +
+
+
+ + +
+
+
+
+ + + + +
+ ) +} +``` + +### Dialog (Modal) Component + +Installation: + +```bash +npx shadcn@latest add dialog +``` + +Basic dialog: + +```tsx +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" + +export function DialogDemo() { + return ( + + + + + + + Edit profile + + Make changes to your profile here. Click save when you're done. + + +
+
+ + +
+
+ + + +
+
+ ) +} +``` + +### Sheet (Slide-over) Component + +Installation: + +```bash +npx shadcn@latest add sheet +``` + +Basic sheet: + +```tsx +import { Button } from "@/components/ui/button" +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet" + +export function SheetDemo() { + return ( + + + + + + + Edit profile + + Make changes to your profile here. Click save when you're done. + + +
+
+ + +
+
+ + +
+
+
+
+ ) +} +``` + +Sheet with side placement: + +```tsx + + + + + + + Settings + + Configure your application settings here. + + + {/* Settings content */} + + +``` + +### Menubar & Navigation + +#### Menubar Component + +Installation: + +```bash +npx shadcn@latest add menubar +``` + +Basic menubar: + +```tsx +import { + Menubar, + MenubarContent, + MenubarItem, + MenubarMenu, + MenubarSeparator, + MenubarShortcut, + MenubarSub, + MenubarSubContent, + MenubarSubTrigger, + MenubarTrigger, +} from "@/components/ui/menubar" + +export function MenubarDemo() { + return ( + + + File + + + New Tab ⌘T + + + New Window ⌘N + + + Share + + Print + + + + Edit + + + Undo ⌘Z + + + Redo ⌘Y + + + + Find + + Search the web + Find... + Find Next + Find Previous + + + + + + ) +} +``` + +### Select (Dropdown) Component + +Installation: + +```bash +npx shadcn@latest add select +``` + +Basic select: + +```tsx +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" + +export function SelectDemo() { + return ( + + ) +} +``` + +Select in form: + +```tsx + ( + + Role + + + + )} +/> +``` + +### Toast Notifications + +Installation: + +```bash +npx shadcn@latest add toast +``` + +Setup toast provider in root layout: + +```tsx +import { Toaster } from "@/components/ui/toaster" + +export default function RootLayout({ children }) { + return ( + + + {children} + + + + ) +} +``` + +Using toast: + +```tsx +import { useToast } from "@/components/ui/use-toast" +import { Button } from "@/components/ui/button" + +export function ToastDemo() { + const { toast } = useToast() + + return ( + + ) +} +``` + +Toast variants: + +```tsx +// Success +toast({ + title: "Success", + description: "Your changes have been saved.", +}) + +// Error +toast({ + variant: "destructive", + title: "Error", + description: "Something went wrong.", +}) + +// With action +toast({ + title: "Uh oh! Something went wrong.", + description: "There was a problem with your request.", + action: Try again, +}) +``` + +### Table Component + +Installation: + +```bash +npx shadcn@latest add table +``` + +Basic 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" }, +] + +export function TableDemo() { + return ( + + A list of your recent invoices. + + + Invoice + Status + Method + Amount + + + + {invoices.map((invoice) => ( + + {invoice.invoice} + {invoice.status} + {invoice.method} + {invoice.amount} + + ))} + +
+ ) +} +``` + +### Charts Component + +Installation: + +```bash +npx shadcn@latest add chart +``` + +The charts component in shadcn/ui is built on **Recharts** - providing direct access to all Recharts capabilities with consistent theming and styling. + +#### ChartContainer and ChartConfig + +The `ChartContainer` wraps your Recharts component and accepts a `config` prop for theming: + +```tsx +import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts" +import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart" + +const chartConfig = { + desktop: { + label: "Desktop", + color: "var(--chart-1)", + }, + mobile: { + label: "Mobile", + color: "var(--chart-2)", + }, +} satisfies import("@/components/ui/chart").ChartConfig + +const chartData = [ + { month: "January", desktop: 186, mobile: 80 }, + { month: "February", desktop: 305, mobile: 200 }, + { month: "March", desktop: 237, mobile: 120 }, +] + +export function BarChartDemo() { + return ( + + + + value.slice(0, 3)} + /> + + + } /> + + + ) +} +``` + +#### ChartConfig with Custom Colors + +You can define custom colors directly in the configuration: + +```tsx +const chartConfig = { + visitors: { + label: "Visitors", + color: "#2563eb", // Custom hex color + theme: { + light: "#2563eb", + dark: "#60a5fa", + }, + }, + sales: { + label: "Sales", + color: "var(--chart-1)", // CSS variable + theme: { + light: "oklch(0.646 0.222 41.116)", + dark: "oklch(0.696 0.182 281.41)", + }, + }, +} satisfies import("@/components/ui/chart").ChartConfig +``` + +#### CSS Variables for Charts + +Add chart color variables to your `globals.css`: + +```css +@layer base { + :root { + /* Chart colors */ + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.546 0.198 38.228); + --chart-4: oklch(0.596 0.151 343.253); + --chart-5: oklch(0.546 0.158 49.157); + } + + .dark { + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.698 0.141 24.311); + --chart-4: oklch(0.676 0.172 171.196); + --chart-5: oklch(0.578 0.192 302.85); + } +} +``` + +#### Line Chart Example + +```tsx +import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts" +import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart" + +const chartConfig = { + price: { + label: "Price", + color: "var(--chart-1)", + }, +} satisfies import("@/components/ui/chart").ChartConfig + +const chartData = [ + { month: "January", price: 186 }, + { month: "February", price: 305 }, + { month: "March", price: 237 }, + { month: "April", price: 203 }, + { month: "May", price: 276 }, +] + +export function LineChartDemo() { + return ( + + + + + `$${value}`} /> + + } /> + + + ) +} +``` + +#### Area Chart Example + +```tsx +import { Area, AreaChart, XAxis, YAxis } from "recharts" +import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltipContent } from "@/components/ui/chart" + +const chartConfig = { + desktop: { label: "Desktop", color: "var(--chart-1)" }, + mobile: { label: "Mobile", color: "var(--chart-2)" }, +} satisfies import("@/components/ui/chart").ChartConfig + +export function AreaChartDemo() { + return ( + + + + + + + } /> + } /> + + + ) +} +``` + +#### Pie Chart Example + +```tsx +import { Pie, PieChart } from "recharts" +import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltipContent } from "@/components/ui/chart" + +const chartConfig = { + chrome: { label: "Chrome", color: "var(--chart-1)" }, + safari: { label: "Safari", color: "var(--chart-2)" }, + firefox: { label: "Firefox", color: "var(--chart-3)" }, +} satisfies import("@/components/ui/chart").ChartConfig + +const pieData = [ + { browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" }, + { browser: "Safari", visitors: 200, fill: "var(--color-safari)" }, + { browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" }, +] + +export function PieChartDemo() { + return ( + + + + } /> + } /> + + + ) +} +``` + +#### ChartTooltipContent Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `labelKey` | string | "label" | Key for tooltip label | +| `nameKey` | string | "name" | Key for tooltip name | +| `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style | +| `hideLabel` | boolean | false | Hide label | +| `hideIndicator` | boolean | false | Hide indicator | + +#### Accessibility + +Enable keyboard navigation and screen reader support: + +```tsx +... +``` + +This adds: +- Keyboard arrow key navigation +- ARIA labels for chart elements +- Screen reader announcements for data values + +## Customization + +### Theming with CSS Variables + +shadcn/ui uses CSS variables for theming. 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%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 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%; + /* ... other dark mode variables */ + } +} +``` + +### Customizing Components + +Since you own the code, customize directly: + +```tsx +// components/ui/button.tsx +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" + +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", + // Add custom variant + custom: "bg-gradient-to-r from-purple-500 to-pink-500 text-white", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + // Add custom size + xl: "h-14 rounded-md px-10 text-lg", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } +``` + +## Next.js Integration + +### App Router Setup + +For Next.js 13+ with App Router, ensure components use `"use client"` directive: + +```tsx +// src/components/ui/button.tsx +"use client" + +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" + +// ... 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 ( + + + {children} + + + + ) +} +``` + +### Server Components + +When using shadcn/ui components in Server Components, wrap them in a Client Component: + +```tsx +// app/dashboard/page.tsx +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { ButtonClient } from "@/components/ui/button-client" + +export default function DashboardPage() { + return ( +
+ + + Dashboard + + + Interactive Button + + +
+ ) +} +``` + +```tsx +// src/components/ui/button-client.tsx +"use client" + +import { Button } from "./button" + +export function ButtonClient(props: React.ComponentProps) { + return