Files
agent/.agent/skills/tech-stack/shadcn-ui-designer/SKILL.md

478 lines
12 KiB
Markdown
Raw Normal View History

---
name: shadcn-ui-designer
description: Design and build modern UI components and pages using shadcn/ui. Creates clean, accessible interfaces with Tailwind CSS following shadcn principles. Use when building UI components, pages, forms, dashboards, or any interface work.
---
# Shadcn UI Designer
Build production-ready UI components using shadcn/ui principles: minimal, accessible, composable, and beautiful by default.
## Core Philosophy
**Design modern, minimal interfaces** with:
- Clean typography (Inter/system fonts, 2-3 weights max)
- Ample whitespace (4px-based spacing: p-1 through p-8)
- Subtle shadows (shadow-sm/md/lg only)
- Accessible contrast (WCAG AA minimum)
- Smooth micro-interactions (200-300ms transitions)
- Professional neutrals (slate/zinc scale) with subtle accents
**Build composable components** that work together seamlessly.
## Quick Start Pattern
```tsx
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
export function MyComponent() {
return (
<div className="container mx-auto p-6 space-y-6">
<div className="space-y-2">
<h1 className="text-2xl font-semibold">Title</h1>
<p className="text-sm text-muted-foreground">Description</p>
</div>
<Card className="shadow-sm">
<CardHeader>
<CardTitle>Section</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Content here */}
</CardContent>
</Card>
</div>
)
}
```
## Design System Rules
### Typography
- **Hierarchy**: `text-2xl` (headings) → `text-base` (body) → `text-sm` (secondary)
- **Weights**: `font-semibold` (600) for emphasis, `font-medium` (500) for labels, `font-normal` (400) for body
- **Colors**: `text-foreground` (primary), `text-muted-foreground` (secondary)
```tsx
<h1 className="text-2xl font-semibold">Page Title</h1>
<p className="text-muted-foreground">Supporting text</p>
```
### Spacing
Use consistent spacing scale:
- **Micro**: `space-y-2` (8px) - within sections
- **Small**: `space-y-4` (16px) - between elements
- **Medium**: `space-y-6` (24px) - between sections
- **Large**: `space-y-8` (32px) - major divisions
```tsx
<div className="container mx-auto p-6 space-y-6">
<section className="space-y-4">
<div className="space-y-2">
{/* Related elements */}
</div>
</section>
</div>
```
### Colors
Use semantic color tokens:
- **Background**: `bg-background`, `bg-card`, `bg-muted`
- **Foreground**: `text-foreground`, `text-muted-foreground`
- **Borders**: `border-border`, `border-input`
- **Primary**: `bg-primary`, `text-primary-foreground`
- **Destructive**: `bg-destructive`, `text-destructive-foreground`
```tsx
<Card className="bg-card text-card-foreground border-border">
<Button className="bg-primary text-primary-foreground">
Primary Action
</Button>
<div className="bg-muted/50 text-muted-foreground">
Subtle highlight
</div>
</Card>
```
### Shadows & Elevation
Three levels only:
- `shadow-sm`: Cards, raised sections (0 1px 2px)
- `shadow-md`: Dropdowns, popovers (0 4px 6px)
- `shadow-lg`: Modals, dialogs (0 10px 15px)
```tsx
<Card className="shadow-sm hover:shadow-md transition-shadow" />
```
### Animations
- **Duration**: 200-300ms
- **Easing**: ease-in-out
- **Use cases**: Hover states, loading states, reveals
```tsx
<Button className="transition-colors duration-200 hover:bg-primary/90">
<Card className="transition-all duration-200 hover:shadow-md hover:scale-[1.02]">
```
### Accessibility
Always include:
- Semantic HTML (`<main>`, `<nav>`, `<article>`)
- ARIA labels on icons/actions
- Focus states (`:focus-visible:ring-2`)
- Keyboard navigation
- WCAG AA contrast (4.5:1 minimum)
```tsx
<button
aria-label="Close dialog"
className="focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
>
<X className="h-4 w-4" />
</button>
```
## Component Patterns
### Dashboard Cards
```tsx
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{stats.map(stat => (
<Card key={stat.id} className="shadow-sm">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
{stat.label}
</CardTitle>
{stat.icon}
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stat.value}</div>
<p className="text-xs text-muted-foreground">
{stat.change}
</p>
</CardContent>
</Card>
))}
</div>
```
### Forms
```tsx
<form className="space-y-6">
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Full Name</Label>
<Input
id="name"
placeholder="Enter your name"
className="max-w-md"
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="name@example.com"
className="max-w-md"
/>
</div>
</div>
<div className="flex gap-3">
<Button type="submit">Submit</Button>
<Button type="button" variant="outline">Cancel</Button>
</div>
</form>
```
### Data Tables
```tsx
<Card className="shadow-sm">
<CardHeader>
<CardTitle>Recent Orders</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Order</TableHead>
<TableHead>Customer</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{orders.map(order => (
<TableRow
key={order.id}
className="hover:bg-muted/50 transition-colors"
>
<TableCell className="font-medium">{order.id}</TableCell>
<TableCell>{order.customer}</TableCell>
<TableCell>
<Badge variant={order.statusVariant}>
{order.status}
</Badge>
</TableCell>
<TableCell className="text-right">
{order.amount}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
```
### Modals/Dialogs
```tsx
<Dialog>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit Profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when done.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Form fields */}
</div>
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
```
### Loading States
```tsx
// Skeleton loading
<Card className="shadow-sm">
<CardHeader>
<Skeleton className="h-4 w-[200px]" />
</CardHeader>
<CardContent className="space-y-3">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-[80%]" />
</CardContent>
</Card>
// Loading button
<Button disabled>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Please wait
</Button>
```
### Empty States
```tsx
<Card className="shadow-sm">
<CardContent className="flex flex-col items-center justify-center py-12 space-y-3">
<div className="p-4 bg-muted rounded-full">
<FileX className="h-8 w-8 text-muted-foreground" />
</div>
<div className="text-center space-y-1">
<h3 className="font-semibold">No results found</h3>
<p className="text-sm text-muted-foreground">
Try adjusting your search
</p>
</div>
<Button variant="outline" size="sm">
Clear filters
</Button>
</CardContent>
</Card>
```
## Layout Patterns
### Container Widths
```tsx
// Full width with constraints
<div className="container mx-auto px-4 max-w-7xl">
// Content-focused (prose)
<div className="container mx-auto px-4 max-w-3xl">
// Form-focused
<div className="container mx-auto px-4 max-w-2xl">
```
### Responsive Grids
```tsx
// Dashboard grid
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
// Content + sidebar
<div className="grid gap-6 lg:grid-cols-[1fr_300px]">
<main>{/* Content */}</main>
<aside>{/* Sidebar */}</aside>
</div>
// Two column split
<div className="grid gap-6 md:grid-cols-2">
```
### Navigation
```tsx
<header className="border-b border-border">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<div className="flex items-center gap-6">
<Logo />
<nav className="hidden md:flex gap-6">
<a href="#" className="text-sm font-medium transition-colors hover:text-primary">
Dashboard
</a>
<a href="#" className="text-sm font-medium text-muted-foreground transition-colors hover:text-foreground">
Projects
</a>
</nav>
</div>
<div className="flex items-center gap-4">
<Button variant="ghost" size="sm">
<Bell className="h-4 w-4" />
</Button>
<Avatar>
<AvatarImage src={user.avatar} />
<AvatarFallback>{user.initials}</AvatarFallback>
</Avatar>
</div>
</div>
</header>
```
## Best Practices
### Component Organization
```tsx
// ✅ Good: Small, focused components
export function UserCard({ user }) {
return (
<Card>
<CardHeader>
<UserAvatar user={user} />
<UserDetails user={user} />
</CardHeader>
</Card>
)
}
// ❌ Avoid: Large monolithic components
export function DashboardPage() {
// 500 lines of JSX...
}
```
### Composability
```tsx
// ✅ Compose shadcn components
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Share</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
```
### State Management
```tsx
// Form state
const [formData, setFormData] = useState({ name: '', email: '' })
// Loading states
const [isLoading, setIsLoading] = useState(false)
// UI states
const [isOpen, setIsOpen] = useState(false)
```
### Error Handling
```tsx
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
className={errors.email ? "border-destructive" : ""}
/>
{errors.email && (
<p className="text-sm text-destructive">{errors.email}</p>
)}
</div>
</form>
```
## Common Shadcn Components
### Essential Components
- **Layout**: Card, Tabs, Sheet, Dialog, Popover, Separator
- **Forms**: Input, Textarea, Select, Checkbox, Radio, Switch, Label
- **Buttons**: Button, Toggle, ToggleGroup
- **Display**: Badge, Avatar, Skeleton, Table
- **Feedback**: Alert, Toast, Progress
- **Navigation**: NavigationMenu, DropdownMenu, Command
### Button Variants
```tsx
<Button>Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button variant="destructive">Delete</Button>
```
### Badge Variants
```tsx
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="destructive">Error</Badge>
```
## Workflow
1. **Understand requirements** - What component/page is needed?
2. **Choose components** - Which shadcn/ui components fit?
3. **Build structure** - Layout and hierarchy first
4. **Apply styling** - Typography, spacing, colors
5. **Add interactions** - Hover states, transitions, focus
6. **Ensure accessibility** - ARIA, keyboard, contrast
7. **Test responsive** - Mobile, tablet, desktop
## Quality Checklist
Before completing:
- [ ] Uses shadcn/ui components appropriately
- [ ] Follows 4px spacing scale (p-2, p-4, p-6, etc.)
- [ ] Uses semantic color tokens (bg-card, text-foreground, etc.)
- [ ] Limited shadow usage (shadow-sm/md/lg only)
- [ ] Smooth transitions (200-300ms duration)
- [ ] ARIA labels on interactive elements
- [ ] Keyboard focus visible (ring-2 ring-primary)
- [ ] WCAG AA contrast ratios
- [ ] Mobile-responsive layout
- [ ] Loading and error states handled
## References
- [Shadcn UI](https://ui.shadcn.com) - Component library
- [Tailwind CSS](https://tailwindcss.com) - Utility classes
- [WCAG 2.1](https://www.w3.org/WAI/WCAG21/quickref/) - Accessibility standards