skills/smithery.ai/shadcn-ui-designer

shadcn-ui-designer

SKILL.md

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

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)
<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
<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
<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)
<Card className="shadow-sm hover:shadow-md transition-shadow" />

Animations

  • Duration: 200-300ms
  • Easing: ease-in-out
  • Use cases: Hover states, loading states, reveals
<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)
<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

<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

<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

<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

<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

// 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

<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

// 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

// 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

<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

// ✅ 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

// ✅ 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

// Form state
const [formData, setFormData] = useState({ name: '', email: '' })

// Loading states
const [isLoading, setIsLoading] = useState(false)

// UI states
const [isOpen, setIsOpen] = useState(false)

Error Handling

<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

<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

<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

Weekly Installs
1
First Seen
12 days ago
Installed on
windsurf1