shadcn-ui by einverne
Guide for implementing shadcn/ui - a collection of beautifully-designed, accessible UI components built with Radix UI and Tailwind CSS. Use when building user interfaces, adding UI components, or implementing design systems in React-based applications.
Content & Writing
115 Stars
19 Forks
Updated Jan 15, 2026, 09:07 AM
Why Use This
This skill provides specialized capabilities for einverne's codebase.
Use Cases
- Developing new features in the einverne repository
- Refactoring existing code to follow einverne standards
- Understanding and working with einverne's codebase structure
Install Guide
2 steps- 1
Skip this step if Ananke is already installed.
- 2
Skill Snapshot
Auto scan of skill assets. Informational only.
Valid SKILL.md
Checks against SKILL.md specification
Source & Community
Skill Stats
SKILL.md 1053 Lines
Total Files 1
Total Size 0 B
License MIT
---
name: shadcn-ui
description: Guide for implementing shadcn/ui - a collection of beautifully-designed, accessible UI components built with Radix UI and Tailwind CSS. Use when building user interfaces, adding UI components, or implementing design systems in React-based applications.
license: MIT
version: 1.0.0
---
# shadcn/ui Skill
shadcn/ui is a collection of beautifully-designed, accessible components and a code distribution platform built with TypeScript, Tailwind CSS, and Radix UI primitives. It's not a traditional component library but a collection of reusable components you can copy and paste into your apps.
## Reference
https://ui.shadcn.com/llms.txt
## When to Use This Skill
Use this skill when:
- Building user interfaces with React-based frameworks (Next.js, Vite, Remix, Astro, etc.)
- Adding pre-built, accessible UI components to applications
- Implementing design systems with Tailwind CSS
- Setting up forms with validation (React Hook Form + Zod)
- Adding data tables, charts, or complex UI patterns
- Implementing dark mode with consistent theming
- Customizing component appearance and behavior
## Core Concepts
### Key Principles
- **Open Code**: Copy components into your project, modify freely
- **Composition**: Built with composable primitives from Radix UI
- **Distribution**: Components distributed via CLI, not npm packages
- **Beautiful Defaults**: Thoughtfully designed with excellent aesthetics
- **AI-Ready**: Structured for easy integration with AI tools
### Architecture
shadcn/ui follows a unique distribution model:
1. **CLI Tool**: Installs and manages components via `npx shadcn@latest`
2. **Component Registry**: Central repository of components
3. **Local Components**: Components live in your `components/ui/` directory
4. **Full Ownership**: You own the code, modify as needed
### Technology Stack
- **TypeScript**: Full type safety
- **Tailwind CSS**: Utility-first styling (v3 and v4 support)
- **Radix UI**: Accessible, unstyled primitives
- **Class Variance Authority**: Component variants
- **React 19**: Compatible with latest React
## Installation & Setup
### Initial Setup
**Using the CLI (Recommended):**
```bash
npx shadcn@latest init
```
The CLI will prompt for:
- Framework preference (Next.js, Vite, etc.)
- TypeScript or JavaScript
- Component installation location
- CSS variables or Tailwind configuration
- Color theme preferences
- Global CSS file location
**Manual Setup:**
1. Install dependencies:
```bash
npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react
```
2. Create `components.json`:
```json
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
```
3. Configure Tailwind:
```ts
// tailwind.config.ts
import type { Config } from "tailwindcss"
const config: Config = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
],
theme: {
extend: {},
},
plugins: [require("tailwindcss-animate")],
}
export default config
```
4. Create utility file:
```ts
// lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
```
### Adding Components
**Via CLI:**
```bash
# Add single component
npx shadcn@latest add button
# Add multiple components
npx shadcn@latest add button card dialog
# Add all components
npx shadcn@latest add --all
```
**What happens when you add a component:**
1. Component files are copied to `components/ui/`
2. Dependencies are automatically installed
3. Component is ready to import and use
## Component Categories
### Form & Input Components
**Button:**
```tsx
import { Button } from "@/components/ui/button"
<Button variant="default">Click me</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline" size="sm">Small</Button>
<Button variant="ghost" size="icon">
<Icon />
</Button>
```
**Input:**
```tsx
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
<div>
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="[email protected]" />
</div>
```
**Form (with validation):**
```tsx
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
const formSchema = z.object({
username: z.string().min(2).max(50),
email: z.string().email(),
})
function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "",
},
})
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
```
**Select:**
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
```
**Checkbox:**
```tsx
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Label htmlFor="terms">Accept terms and conditions</Label>
</div>
```
**Date Picker:**
```tsx
import { Calendar } from "@/components/ui/calendar"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Button } from "@/components/ui/button"
import { CalendarIcon } from "lucide-react"
import { format } from "date-fns"
const [date, setDate] = useState<Date>()
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : "Pick a date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar mode="single" selected={date} onSelect={setDate} />
</PopoverContent>
</Popover>
```
### Layout & Navigation
**Card:**
```tsx
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card Description</CardDescription>
</CardHeader>
<CardContent>
<p>Card Content</p>
</CardContent>
<CardFooter>
<p>Card Footer</p>
</CardFooter>
</Card>
```
**Tabs:**
```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">Account settings</TabsContent>
<TabsContent value="password">Password settings</TabsContent>
</Tabs>
```
**Accordion:**
```tsx
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern.
</AccordionContent>
</AccordionItem>
</Accordion>
```
**Navigation Menu:**
```tsx
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Item One</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuLink>Link</NavigationMenuLink>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
```
### Overlays & Dialogs
**Dialog:**
```tsx
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>
This action cannot be undone.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
```
**Drawer:**
```tsx
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer"
<Drawer>
<DrawerTrigger>Open</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Are you sure?</DrawerTitle>
<DrawerDescription>This action cannot be undone.</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose>Cancel</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
```
**Popover:**
```tsx
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
<Popover>
<PopoverTrigger>Open</PopoverTrigger>
<PopoverContent>Place content here.</PopoverContent>
</Popover>
```
**Toast:**
```tsx
import { useToast } from "@/hooks/use-toast"
import { Button } from "@/components/ui/button"
const { toast } = useToast()
<Button
onClick={() => {
toast({
title: "Scheduled: Catch up",
description: "Friday, February 10, 2023 at 5:57 PM",
})
}}
>
Show Toast
</Button>
```
**Command:**
```tsx
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>Calendar</CommandItem>
<CommandItem>Search Emoji</CommandItem>
<CommandItem>Calculator</CommandItem>
</CommandGroup>
</CommandList>
</Command>
```
### Feedback & Status
**Alert:**
```tsx
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
<Alert>
<AlertTitle>Heads up!</AlertTitle>
<AlertDescription>
You can add components to your app using the CLI.
</AlertDescription>
</Alert>
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>
Your session has expired. Please log in again.
</AlertDescription>
</Alert>
```
**Progress:**
```tsx
import { Progress } from "@/components/ui/progress"
<Progress value={33} />
```
**Skeleton:**
```tsx
import { Skeleton } from "@/components/ui/skeleton"
<div className="flex items-center space-x-4">
<Skeleton className="h-12 w-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
</div>
</div>
```
### Display Components
**Table:**
```tsx
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>INV001</TableCell>
<TableCell>Paid</TableCell>
<TableCell>$250.00</TableCell>
</TableRow>
</TableBody>
</Table>
```
**Data Table (with sorting/filtering):**
```bash
npx shadcn@latest add data-table
```
**Avatar:**
```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
```
**Badge:**
```tsx
import { Badge } from "@/components/ui/badge"
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
```
## Theming & Customization
### Dark Mode Setup
**Next.js (App Router):**
1. Install next-themes:
```bash
npm install next-themes
```
2. Create theme provider:
```tsx
// components/theme-provider.tsx
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
```
3. Wrap app with provider:
```tsx
// app/layout.tsx
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
)
}
```
4. Add theme toggle:
```tsx
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}
```
### Color Customization
**Using CSS Variables:**
```css
/* globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--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%;
/* ... */
}
}
```
**Using Tailwind Config:**
```ts
// tailwind.config.ts
export default {
theme: {
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))",
},
// ...
},
},
},
}
```
### Component Customization
Since components live in your codebase, you can modify them directly:
```tsx
// components/ui/button.tsx
// Modify variants, add new ones, change styles
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground",
destructive: "bg-destructive text-destructive-foreground",
outline: "border border-input bg-background",
// 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",
},
}
)
```
## Advanced Patterns
### Server Actions (Next.js)
```tsx
// app/actions.ts
"use server"
import { z } from "zod"
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
})
export async function createUser(formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get("email"),
password: formData.get("password"),
})
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// Create user
}
// app/signup/page.tsx
import { createUser } from "@/app/actions"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export default function SignupPage() {
return (
<form action={createUser}>
<Input name="email" type="email" />
<Input name="password" type="password" />
<Button type="submit">Sign up</Button>
</form>
)
}
```
### Reusable Form Patterns
```tsx
// lib/form-utils.ts
import { UseFormReturn } from "react-hook-form"
import { FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
export function TextFormField({
form,
name,
label,
placeholder,
type = "text",
}: {
form: UseFormReturn<any>
name: string
label: string
placeholder?: string
type?: string
}) {
return (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem>
<FormLabel>{label}</FormLabel>
<FormControl>
<Input type={type} placeholder={placeholder} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)
}
```
### Responsive Component Composition
```tsx
import { useMediaQuery } from "@/hooks/use-media-query"
import { Dialog, DialogContent } from "@/components/ui/dialog"
import { Drawer, DrawerContent } from "@/components/ui/drawer"
export function ResponsiveDialog({ children, ...props }) {
const isDesktop = useMediaQuery("(min-width: 768px)")
if (isDesktop) {
return (
<Dialog {...props}>
<DialogContent>{children}</DialogContent>
</Dialog>
)
}
return (
<Drawer {...props}>
<DrawerContent>{children}</DrawerContent>
</Drawer>
)
}
```
## Best Practices
1. **Use TypeScript**: Leverage full type safety for better DX
2. **Customize Components**: Modify components directly in your codebase
3. **Compose Primitives**: Build complex UIs by composing simple components
4. **Follow Accessibility**: Components are built on accessible Radix UI primitives
5. **Use Form Validation**: Integrate React Hook Form + Zod for robust forms
6. **Dark Mode**: Implement theme switching for better UX
7. **Responsive Design**: Use Tailwind responsive utilities
8. **Performance**: Use code splitting and lazy loading for large component sets
9. **Consistent Spacing**: Use Tailwind spacing scale consistently
10. **Icon Library**: Use lucide-react for consistent icons
## Framework-Specific Setup
### Next.js
- Support for App Router and Pages Router
- Server Components compatibility
- Server Actions integration
### Vite
- Fast development with HMR
- Easy setup with TypeScript
### Remix
- Route-based architecture
- Progressive enhancement
### Astro
- Static site generation
- Islands architecture
### Laravel (Inertia.js)
- Backend integration with Laravel
- React frontend with Inertia
## Common Patterns
### Loading States
```tsx
import { Skeleton } from "@/components/ui/skeleton"
export function UserCardSkeleton() {
return (
<div className="flex items-center space-x-4">
<Skeleton className="h-12 w-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
</div>
</div>
)
}
export function UserCard({ user }: { user?: User }) {
if (!user) return <UserCardSkeleton />
return (
<div className="flex items-center space-x-4">
<Avatar>
<AvatarImage src={user.avatar} />
<AvatarFallback>{user.initials}</AvatarFallback>
</Avatar>
<div>
<p className="text-sm font-medium">{user.name}</p>
<p className="text-sm text-muted-foreground">{user.email}</p>
</div>
</div>
)
}
```
### Error Handling
```tsx
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { AlertCircle } from "lucide-react"
export function ErrorAlert({ error }: { error: Error }) {
return (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error.message}</AlertDescription>
</Alert>
)
}
```
### Confirmation Dialogs
```tsx
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
export function DeleteConfirmation({ onConfirm }: { onConfirm: () => void }) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">Delete</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm}>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
```
## Troubleshooting
### Common Issues
1. **"Module not found" errors**
- Check path aliases in `tsconfig.json`
- Verify `components.json` aliases match your project structure
- Ensure components are installed in correct directory
2. **Styling not applied**
- Verify Tailwind CSS is configured correctly
- Check `globals.css` imports CSS variables
- Ensure `tailwindcss-animate` plugin is installed
3. **Dark mode not working**
- Check ThemeProvider is wrapping your app
- Verify `suppressHydrationWarning` on `<html>` tag
- Ensure dark mode classes are defined in CSS
4. **Form validation issues**
- Install required packages: `react-hook-form`, `@hookform/resolvers`, `zod`
- Check schema matches form fields
- Verify resolver is configured correctly
5. **TypeScript errors**
- Update `@types/react` and `@types/react-dom`
- Check component prop types
- Ensure TypeScript version is compatible (>= 4.5)
## Resources
- Documentation: https://ui.shadcn.com
- GitHub: https://github.com/shadcn-ui/ui
- Component Registry: https://ui.shadcn.com/docs/components
- Examples: https://ui.shadcn.com/examples
- Figma Design Kit: https://ui.shadcn.com/figma
- v0 (AI UI Generator): https://v0.dev
## Implementation Checklist
When implementing shadcn/ui:
- [ ] Run `npx shadcn@latest init` to set up project
- [ ] Configure Tailwind CSS and path aliases
- [ ] Set up dark mode with ThemeProvider
- [ ] Install required components via CLI
- [ ] Create utility functions (cn helper)
- [ ] Set up form handling (React Hook Form + Zod)
- [ ] Configure icons (lucide-react)
- [ ] Implement theme toggle component
- [ ] Test components in both light and dark modes
- [ ] Customize color palette if needed
- [ ] Add loading states with Skeleton
- [ ] Implement error handling patterns
- [ ] Test accessibility features
- [ ] Optimize bundle size (tree-shaking)
- [ ] Add responsive design utilities
Name Size