UI design patterns for React + TailwindCSS. Use when creating new UI components, styling elements, implementing dark mode support, or working on any React component that needs consistent styling.
All UI components MUST support dark mode using Tailwind's class strategy.
// Always provide dark: variants
className="bg-white dark:bg-slate-900 text-slate-900 dark:text-white"
brand-500 / brand-600 - Primary buttons, linksbrand-100 / dark:brand-900/30 - Highlighted backgroundsbrand-700 / dark:brand-300 - Text on brand backgroundsslate-50 / dark:slate-950 - Page backgroundsslate-100 / dark:slate-800 - Secondary/card backgroundsslate-200 / dark:slate-700 - Bordersslate-400 - Muted textslate-700 / dark:white - Primary textemerald-* (done, approved)amber-* (pending, paused)red-* (failed, stopped)Use pre-defined component classes for consistency:
// Buttons
<button className="btn btn-primary">Create</button>
<button className="btn btn-secondary">Cancel</button>
<button className="btn btn-ghost">Refresh</button>
<button className="btn btn-danger">Delete</button>
// Inputs
<input className="input" />
<textarea className="input resize-none" />
// Select dropdowns
<select className="select w-[180px]">
<option>Option</option>
</select>
// Cards
<div className="card p-4">Content</div>
Use consistent status badge pattern with icons:
const STATUS_CONFIG = {
backlog: { icon: "inbox", classes: "bg-slate-100 text-slate-700 border-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700" },
ready: { icon: "circle-dashed", classes: "bg-slate-100 text-slate-700 border-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700" },
in_progress: { icon: "timer", classes: "bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-800" },
pending_review: { icon: "eye", classes: "bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:border-amber-800" },
done: { icon: "check-circle", classes: "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800" },
};
// Badge structure
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium border whitespace-nowrap {classes}">
<Icon className="w-3.5 h-3.5" />
{label}
</span>
// Page titles
<h1 className="text-2xl font-semibold text-slate-900 dark:text-white">
// Section titles
<h2 className="text-lg font-semibold text-slate-900 dark:text-white">
// Subsection headers (uppercase)
<h3 className="text-xs font-bold text-slate-400 uppercase tracking-wider">
<div className="flex flex-1 min-h-0 overflow-hidden rounded-lg border border-slate-200 dark:border-slate-800">
<div className="w-1/2 border-r border-slate-200 dark:border-slate-800 p-6 overflow-y-auto bg-white dark:bg-slate-900">
{/* Left panel */}
</div>
<div className="w-1/2 flex flex-col min-w-0">
{/* Right panel */}
</div>
</div>
<div className="p-4 bg-slate-50 dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700">
<h3 className="text-sm font-medium text-slate-900 dark:text-white mb-3">
Section Title
</h3>
{/* Content */}
</div>
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="absolute inset-0 bg-black/50" onClick={onClose} />
<div className="relative bg-white dark:bg-slate-800 rounded-lg shadow-xl w-full max-w-lg mx-4 border border-slate-200 dark:border-slate-700">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-200 dark:border-slate-700">
<h2 className="text-lg font-semibold text-slate-900 dark:text-white">Title</h2>
<button onClick={onClose}>...</button>
</div>
{/* Body */}
<div className="px-6 py-4">...</div>
{/* Footer */}
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-slate-200 dark:border-slate-700">
<button className="btn btn-secondary">Cancel</button>
<button className="btn btn-primary">Submit</button>
</div>
</div>
</div>
| Size | Usage |
|---|---|
gap-1.5 |
Icon + text in badges |
gap-2 |
Button groups |
gap-3 |
Form fields |
gap-4 |
Section spacing |
gap-6 |
Major section spacing |
p-4 |
Card padding |
p-6 |
Modal/panel padding |
Use .scrollbar-thin for custom scrollbars:
<div className="overflow-y-auto scrollbar-thin">
// Skeleton loader
<div className="animate-pulse bg-slate-200 dark:bg-slate-700 rounded h-4 w-32" />
// Spinner
<svg className="animate-spin h-5 w-5 text-blue-500" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
<div className="flex flex-col items-center justify-center py-12 text-center">
<Icon className="w-12 h-12 text-slate-300 dark:text-slate-600 mb-4" />
<h3 className="text-lg font-medium text-slate-900 dark:text-white mb-2">
No items found
</h3>
<p className="text-sm text-slate-500 dark:text-slate-400 mb-4">
Get started by creating your first item.
</p>
<button className="btn btn-primary">Create Item</button>
</div>