- Learn
- Stack Essentials
- shadcn/ui
- Introduction to shadcn/ui
Understand shadcn/ui's unique approach to component libraries and why AI tools prefer it.
Introduction to shadcn/ui
shadcn/ui is the component library of choice for AI coding tools. Unlike traditional libraries, you own the code—making it perfect for AI-assisted customization.
What is shadcn/ui?
shadcn/ui is not a component library. It's a collection of reusable components that you copy into your project:
Traditional Library:
npm install some-ui → node_modules → import Component
shadcn/ui:
npx shadcn add button → components/ui/button.tsx → Your code
Key Differences
| Aspect | Traditional Library | shadcn/ui |
|---|---|---|
| Installation | npm package | CLI copies files |
| Code location | node_modules | Your codebase |
| Customization | Override styles | Edit source |
| Updates | npm update | Manual or regenerate |
| Bundle size | Full library | Only what you use |
Why AI Tools Love shadcn/ui
1. Code Ownership
AI can read and modify your components:
// components/ui/button.tsx - Fully accessible
const buttonVariants = cva(
"inline-flex items-center justify-center...",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground...",
destructive: "bg-destructive text-destructive-foreground...",
},
},
}
)
AI can add variants, modify styles, or extend functionality.
2. Consistent Patterns
Every component follows the same structure:
// All components have:
// - TypeScript types
// - Tailwind styling
// - Accessible by default
// - Variant support via class-variance-authority
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
size?: 'default' | 'sm' | 'lg' | 'icon'
}
3. v0 Integration
Vercel's v0 generates shadcn/ui components directly:
v0 prompt: "Create a pricing table"
v0 output: Component using shadcn/ui Card, Button, Badge
4. Tailwind CSS Native
No style conflicts—everything is Tailwind:
// All styling is Tailwind classes
<Button className="mt-4 w-full">
Custom styles just work
</Button>
Architecture
Built on Radix UI
shadcn/ui uses Radix primitives for accessibility:
User Interface (what you see)
↓
shadcn/ui Components (styling + API)
↓
Radix UI Primitives (accessibility + behavior)
Radix provides:
- Keyboard navigation
- Screen reader support
- Focus management
- ARIA attributes
shadcn/ui adds:
- Tailwind CSS styling
- Consistent design tokens
- Component variants
Component Structure
Every component follows this pattern:
// components/ui/card.tsx
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
export { Card }
Key elements:
forwardReffor ref forwardingcn()utility for class merging- Tailwind classes for styling
- Accepts additional className
What's Included
Core Components
Layout:
- Card, Separator, Aspect Ratio
- Scroll Area, Resizable
Forms:
- Button, Input, Textarea, Select
- Checkbox, Radio, Switch, Slider
- Form (with react-hook-form)
Feedback:
- Alert, Toast, Progress
- Skeleton, Spinner
Navigation:
- Tabs, Navigation Menu, Breadcrumb
- Sidebar, Pagination
Overlay:
- Dialog, Drawer, Sheet
- Popover, Tooltip, Dropdown Menu
- Alert Dialog, Context Menu
Data Display:
- Table, Data Table
- Avatar, Badge, Calendar
The cn() Utility
Central to shadcn/ui:
// lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
What it does:
// Merges classes intelligently
cn("px-4 py-2", "px-8") // "px-8 py-2" - later px wins
cn("bg-red-500", condition && "bg-blue-500") // conditional classes
cn(baseStyles, className) // combines base with custom
AI-Generated Patterns
What AI Typically Generates
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function LoginForm() {
return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Login</CardTitle>
</CardHeader>
<CardContent>
<form className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="m@example.com" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" />
</div>
<Button type="submit" className="w-full">
Sign In
</Button>
</form>
</CardContent>
</Card>
)
}
Prompting for Components
"Create a settings page using shadcn/ui with:
- Card container
- Form with Input and Switch components
- Save button with loading state"
"Build a data table using shadcn/ui Table with:
- Sortable columns
- Row selection
- Pagination controls"
Recognizing shadcn/ui
Import Patterns
// shadcn/ui imports from your project
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Dialog } from "@/components/ui/dialog"
// Not from node_modules like:
// import { Button } from '@some-library/button'
File Structure
components/
└── ui/
├── button.tsx
├── card.tsx
├── dialog.tsx
├── input.tsx
└── ...
The @/ Alias
shadcn/ui projects typically use path aliases:
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
Summary
- shadcn/ui copies components into your project
- You own the code—full customization control
- Built on Radix—accessible by default
- Tailwind CSS—consistent styling approach
- AI-friendly—code is readable and modifiable
- v0 compatible—generates shadcn/ui components directly
Next Steps
Learn how to install and set up shadcn/ui in your Next.js project.