Quick reference for implementing common UI patterns using the Afterthought design system.
<button className="btn-primary">Click me</button><button style={{ background: 'var(--accent-primary)' }}>
Click me
</button><div className="bg-bg-secondary text-text-primary">
Content
</div>// Primary Button
<button className="btn-primary">
Process Image
</button>
// Secondary Button
<button className="btn-secondary">
Cancel
</button>
// Ghost Button (for toolbars)
<button className="btn-ghost">
<IconSettings className="w-4 h-4" />
</button>
// Danger Button
<button className="btn-danger">
Delete
</button>
// Disabled Button
<button className="btn-primary opacity-50 cursor-not-allowed" disabled>
Loading...
</button>
// Button with Icon
<button className="btn-primary flex items-center gap-2">
<IconPlay className="w-4 h-4" />
Process
</button>// Basic Card
<div className="card">
<h3 className="text-lg font-semibold mb-2">Card Title</h3>
<p className="text-text-secondary text-sm">Card content goes here.</p>
</div>
// Interactive Card (clickable)
<div className="card-interactive" onClick={handleClick}>
<h3 className="text-lg font-semibold mb-2">Clickable Card</h3>
<p className="text-text-secondary text-sm">Hover for animation.</p>
</div>
// Card with Header and Footer
<div className="card">
{/* Header */}
<div className="pb-4 mb-4 border-b border-border-subtle">
<h2 className="text-xl font-semibold">Settings</h2>
</div>
{/* Content */}
<div className="space-y-4">
<p className="text-text-secondary">Card content</p>
</div>
{/* Footer */}
<div className="pt-4 mt-4 border-t border-border-subtle flex justify-end gap-3">
<button className="btn-secondary">Cancel</button>
<button className="btn-primary">Save</button>
</div>
</div>
// Compact Card (tighter spacing)
<div className="bg-bg-secondary border border-border-subtle rounded-lg p-4 hover:border-border-default transition-all">
<div className="flex items-center justify-between">
<span className="font-medium">Compact Item</span>
<span className="text-text-tertiary text-sm">Value</span>
</div>
</div>// Text Input
<div className="space-y-2">
<label className="block text-sm font-medium text-text-secondary">
Email
</label>
<input
type="email"
className="input"
placeholder="Enter your email"
/>
</div>
// Input with Error
<div className="space-y-2">
<label className="block text-sm font-medium text-text-secondary">
API Key
</label>
<input
type="password"
className="input border-error focus:border-error focus:ring-error/20"
placeholder="sk-..."
/>
<p className="text-error text-xs">Invalid API key format</p>
</div>
// Input with Success
<div className="space-y-2">
<label className="block text-sm font-medium text-text-secondary">
Username
</label>
<input
type="text"
className="input border-success focus:border-success focus:ring-success/20"
placeholder="john_doe"
value="john_doe"
/>
<p className="text-success text-xs flex items-center gap-1">
<IconCheck className="w-3 h-3" />
Username available
</p>
</div>
// Textarea
<textarea
className="input resize-none"
rows={4}
placeholder="Enter description..."
/>
// Select Dropdown
<select className="input">
<option>Choose an option</option>
<option>Option 1</option>
<option>Option 2</option>
</select>// Status Badges
<span className="badge-success">Completed</span>
<span className="badge-warning">Pending</span>
<span className="badge-error">Failed</span>
<span className="badge-info">Processing</span>
// Category Badges
<span className="badge-work">Work</span>
<span className="badge-personal">Personal</span>
<span className="badge-reference">Reference</span>
<span className="badge-idea">Idea</span>
<span className="badge-other">Other</span>
// Custom Badge
<span className="px-2 py-1 bg-purple-500/10 text-purple-400 text-xs font-medium rounded-md">
Custom
</span>
// Badge with Icon
<span className="badge-success flex items-center gap-1">
<IconCheck className="w-3 h-3" />
Verified
</span>
// Badge with Count
<span className="badge bg-accent/10 text-accent">
Tasks <span className="ml-1 font-bold">12</span>
</span>// Vertical List
<div className="space-y-3">
{items.map((item) => (
<div
key={item.id}
className="p-4 border border-border-subtle rounded-lg hover:bg-surface-hover transition-all"
>
<h3 className="font-medium">{item.title}</h3>
<p className="text-text-tertiary text-sm mt-1">{item.description}</p>
</div>
))}
</div>
// Compact List
<div className="space-y-2">
{items.map((item) => (
<div
key={item.id}
className="px-3 py-2 rounded-md hover:bg-surface-hover transition-all cursor-pointer"
>
<span className="text-sm">{item.name}</span>
</div>
))}
</div>
// List with Separators
<div className="divide-y divide-border-subtle">
{items.map((item) => (
<div key={item.id} className="py-4 first:pt-0 last:pb-0">
{item.content}
</div>
))}
</div>
// Two-Column List
<div className="grid grid-cols-2 gap-4">
{items.map((item) => (
<div key={item.id} className="card">
{item.content}
</div>
))}
</div>// Modal Container
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/70 backdrop-blur-sm"
onClick={onClose}
/>
{/* Modal */}
<div className="relative bg-bg-elevated border border-border-emphasis rounded-2xl shadow-modal max-w-2xl w-[90vw] max-h-[85vh] overflow-hidden flex flex-col">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-border-subtle">
<h2 className="text-xl font-semibold">Modal Title</h2>
<button
onClick={onClose}
className="text-text-tertiary hover:text-text-primary transition-colors"
>
<IconX className="w-5 h-5" />
</button>
</div>
{/* Content (scrollable) */}
<div className="flex-1 overflow-y-auto px-6 py-4">
<p className="text-text-secondary">Modal content goes here...</p>
</div>
{/* Footer */}
<div className="flex justify-end gap-3 px-6 py-4 border-t border-border-subtle">
<button className="btn-secondary" onClick={onClose}>
Cancel
</button>
<button className="btn-primary" onClick={onSave}>
Save Changes
</button>
</div>
</div>
</div>
// Small Modal (Confirmation)
<div className="bg-bg-elevated border border-border-emphasis rounded-xl shadow-modal p-6 max-w-md">
<h3 className="text-lg font-semibold mb-2">Delete Image?</h3>
<p className="text-text-secondary text-sm mb-6">
This action cannot be undone. The image will be permanently deleted.
</p>
<div className="flex justify-end gap-3">
<button className="btn-secondary">Cancel</button>
<button className="btn-danger">Delete</button>
</div>
</div>// Spinner
<div className="w-6 h-6 border-2 border-border-subtle border-t-accent rounded-full animate-spin" />
// Button Loading
<button className="btn-primary opacity-75 cursor-wait" disabled>
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin mr-2" />
Processing...
</button>
// Skeleton Card
<div className="card">
<div className="skeleton h-6 w-3/4 mb-3" />
<div className="skeleton h-4 w-full mb-2" />
<div className="skeleton h-4 w-5/6" />
</div>
// Full Page Loading
<div className="flex items-center justify-center h-screen">
<div className="text-center">
<div className="w-12 h-12 border-4 border-border-emphasis border-t-accent rounded-full animate-spin mx-auto mb-4" />
<p className="text-text-secondary">Loading...</p>
</div>
</div>
// Inline Loading Text
<div className="flex items-center gap-2 text-text-secondary">
<div className="w-4 h-4 border-2 border-text-tertiary border-t-accent rounded-full animate-spin" />
<span>Processing image...</span>
</div>// Basic Empty State
<div className="empty-state">
<div className="text-6xl mb-4 opacity-50">📸</div>
<h3 className="empty-state-title">No images yet</h3>
<p className="empty-state-description">
Capture an image to get started. We'll automatically detect and analyze it.
</p>
<button className="btn-primary mt-6">
Learn How
</button>
</div>
// Empty State with Action
<div className="empty-state">
<div className="w-16 h-16 bg-surface rounded-xl flex items-center justify-center mb-4 mx-auto">
<IconInbox className="w-8 h-8 text-text-tertiary" />
</div>
<h3 className="empty-state-title">No pending tasks</h3>
<p className="empty-state-description">
All caught up! New tasks will appear here when images are processed.
</p>
</div>
// Empty Search Results
<div className="empty-state">
<div className="text-4xl mb-4">🔍</div>
<h3 className="empty-state-title">No results found</h3>
<p className="empty-state-description">
Try adjusting your search or filters to find what you're looking for.
</p>
<button className="btn-secondary mt-4">
Clear Filters
</button>
</div>// Simple Tooltip
<div className="relative group inline-block">
<button className="btn-ghost">
<IconInfo className="w-4 h-4" />
</button>
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-bg-elevated border border-border-emphasis rounded text-xs whitespace-nowrap opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all">
More information
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1 border-4 border-transparent border-t-bg-elevated" />
</div>
</div>// Success Toast
<div className="bg-bg-elevated border border-success/20 rounded-lg shadow-card p-4 flex items-start gap-3 max-w-md">
<div className="w-5 h-5 rounded-full bg-success/10 flex items-center justify-center flex-shrink-0">
<IconCheck className="w-3 h-3 text-success" />
</div>
<div className="flex-1">
<h4 className="font-medium text-sm mb-1">Image processed</h4>
<p className="text-text-secondary text-xs">
Found 3 tasks and extracted text successfully.
</p>
</div>
<button className="text-text-tertiary hover:text-text-primary">
<IconX className="w-4 h-4" />
</button>
</div>
// Error Toast
<div className="bg-bg-elevated border border-error/20 rounded-lg shadow-card p-4 flex items-start gap-3 max-w-md">
<div className="w-5 h-5 rounded-full bg-error/10 flex items-center justify-center flex-shrink-0">
<IconAlertCircle className="w-3 h-3 text-error" />
</div>
<div className="flex-1">
<h4 className="font-medium text-sm mb-1">Processing failed</h4>
<p className="text-text-secondary text-xs">
Invalid API key. Please check your settings.
</p>
</div>
</div>// Page Header
<header className="bg-bg-secondary border-b border-border-subtle px-6 py-4 flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">Afterthought</h1>
<p className="text-text-tertiary text-sm mt-1">
Image Manager with AI
</p>
</div>
<button className="btn-ghost">
<IconSettings className="w-5 h-5" />
</button>
</header>
// Section Header
<div className="mb-6">
<h2 className="text-xl font-semibold mb-1">Recent Images</h2>
<p className="text-text-secondary text-sm">
Automatically detected from your Pictures folder
</p>
</div>
// Section with Action
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-xl font-semibold">Tasks</h2>
<p className="text-text-tertiary text-sm">12 pending</p>
</div>
<button className="btn-secondary text-sm">
Clear completed
</button>
</div>// Simple Tabs
<div className="flex gap-2 border-b border-border-subtle">
<button
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === 'all'
? 'border-accent text-text-primary'
: 'border-transparent text-text-tertiary hover:text-text-secondary'
}`}
onClick={() => setActiveTab('all')}
>
All
</button>
<button
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === 'pending'
? 'border-accent text-text-primary'
: 'border-transparent text-text-tertiary hover:text-text-secondary'
}`}
onClick={() => setActiveTab('pending')}
>
Pending
</button>
<button
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === 'done'
? 'border-accent text-text-primary'
: 'border-transparent text-text-tertiary hover:text-text-secondary'
}`}
onClick={() => setActiveTab('done')}
>
Done
</button>
</div>
// Pill Tabs
<div className="flex gap-2 p-1 bg-bg-secondary rounded-lg">
<button
className={`px-4 py-2 text-sm font-medium rounded-md transition-all ${
activeTab === 'all'
? 'bg-surface text-text-primary'
: 'text-text-tertiary hover:text-text-secondary'
}`}
onClick={() => setActiveTab('all')}
>
All
</button>
<button
className={`px-4 py-2 text-sm font-medium rounded-md transition-all ${
activeTab === 'pending'
? 'bg-surface text-text-primary'
: 'text-text-tertiary hover:text-text-secondary'
}`}
onClick={() => setActiveTab('pending')}
>
Pending
</button>
</div>// Checkbox
<label className="flex items-center gap-3 cursor-pointer group">
<div className={`w-5 h-5 rounded border-2 flex items-center justify-center transition-all ${
checked
? 'bg-accent border-accent'
: 'border-border-emphasis group-hover:border-accent'
}`}>
{checked && (
<IconCheck className="w-3 h-3 text-white" />
)}
</div>
<span className="text-sm text-text-primary">
Remember me
</span>
</label>
// Radio Button
<label className="flex items-center gap-3 cursor-pointer group">
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all ${
selected
? 'border-accent'
: 'border-border-emphasis group-hover:border-accent'
}`}>
{selected && (
<div className="w-2.5 h-2.5 rounded-full bg-accent" />
)}
</div>
<span className="text-sm text-text-primary">
Option 1
</span>
</label>// Simple Progress Bar
<div className="w-full bg-surface rounded-full h-2 overflow-hidden">
<div
className="bg-accent h-full transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
// Progress Bar with Label
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-text-secondary">Processing...</span>
<span className="text-text-primary font-medium">{progress}%</span>
</div>
<div className="w-full bg-surface rounded-full h-2 overflow-hidden">
<div
className="bg-accent h-full transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
</div>
// Multi-segment Progress
<div className="w-full flex gap-1 h-2">
<div className="bg-success flex-1 rounded-l-full" />
<div className="bg-warning flex-1" />
<div className="bg-surface flex-1 rounded-r-full" />
</div>// Good: Descriptive, reusable pattern
<div className="flex items-center justify-between p-4 bg-bg-secondary rounded-lg hover:bg-surface-hover transition-all">
// Avoid: Too specific, hard to maintain
<div style={{ padding: '16px', background: '#141414', borderRadius: '8px' }}>// Good: Clear purpose
<span className="badge-success">Completed</span>
// Avoid: Implementation detail
<span className="bg-green-100 text-green-800 px-2 py-1 rounded text-xs"><div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{/* Automatically adjusts: 1 column mobile, 2 tablet, 3 desktop */}
</div>// All colors are dark by default
// No need for dark: prefix
<div className="bg-bg-secondary text-text-primary">// Use built-in transitions
<div className="transition-all duration-150 hover:-translate-y-0.5">
Smooth hover effect
</div><div className="flex items-start gap-3 p-4 border border-border-subtle rounded-lg hover:bg-surface-hover transition-all">
{/* Checkbox */}
<button className="w-5 h-5 mt-0.5 rounded border-2 border-border-emphasis hover:border-accent transition-colors" />
{/* Content */}
<div className="flex-1 min-w-0">
<h3 className="font-medium text-text-primary mb-1">Task title</h3>
<p className="text-sm text-text-secondary mb-2">Task description</p>
{/* Metadata */}
<div className="flex items-center gap-2 flex-wrap">
<span className="badge-work">Work</span>
<span className="text-xs text-text-tertiary">✨ 95% confidence</span>
</div>
</div>
{/* Priority */}
<span className="px-2 py-1 bg-error/10 text-error text-xs font-medium rounded-md">
High
</span>
</div><div className="card-interactive flex gap-4">
{/* Thumbnail */}
<div className="w-20 h-20 bg-surface rounded-lg overflow-hidden flex-shrink-0 border border-border-subtle">
<img src={thumbnail} alt="" className="w-full h-full object-cover" />
</div>
{/* Content */}
<div className="flex-1 min-w-0">
<h3 className="font-medium text-text-primary truncate mb-1">
Image_2024.png
</h3>
<p className="text-sm text-text-tertiary mb-2">
2 hours ago
</p>
<div className="flex gap-2">
<span className="badge-work">Work</span>
<span className="badge-success">Processed</span>
</div>
</div>
{/* Actions */}
<button className="btn-primary self-start">
Process
</button>
</div>Remember: Consistency is key. Use these patterns throughout your app for a cohesive user experience.