Consistent Tailwind CSS patterns for React/Next.js applications. Use when styling components with Tailwind, adding responsive design, implementing dark mode, or organizing utility classes.
Consistent patterns for Tailwind CSS in React/Next.js applications.
Always organize classes in this order for readability:
className={cn(
// 1. Layout (position, display, flex/grid)
"relative flex flex-col",
// 2. Sizing (width, height, padding, margin)
"w-full max-w-md p-4 mx-auto",
// 3. Visual (background, border, shadow)
"bg-white border border-gray-200 rounded-lg shadow-sm",
// 4. Typography (font, text, color)
"text-sm font-medium text-gray-900",
// 5. States (hover, focus, active, disabled)
"hover:bg-gray-50 focus:ring-2 focus:ring-blue-500",
// 6. Responsive (sm:, md:, lg:, xl:)
"md:flex-row md:p-6",
// 7. Dark mode
"dark:bg-gray-800 dark:text-white"
)}
Use clsx + tailwind-merge for conditional classes:
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Usage
<div className={cn(
"base-classes",
isActive && "active-classes",
variant === "primary" && "primary-classes"
)} />
// Start with mobile, add breakpoints for larger screens
<div className="
flex flex-col gap-2 // Mobile: stack vertically
md:flex-row md:gap-4 // Tablet+: horizontal row
lg:gap-6 // Desktop: more spacing
" />
sm: - 640px+ (large phones)md: - 768px+ (tablets)lg: - 1024px+ (laptops)xl: - 1280px+ (desktops)2xl: - 1536px+ (large screens)<h1 className="text-2xl md:text-3xl lg:text-4xl font-bold" />
<p className="text-sm md:text-base" />
/* globals.css */
:root {
--background: 255 255 255;
--foreground: 10 10 10;
}
.dark {
--background: 10 10 10;
--foreground: 255 255 255;
}
// tailwind.config.ts
theme: {
extend: {
colors: {
background: 'rgb(var(--background) / <alpha-value>)',
foreground: 'rgb(var(--foreground) / <alpha-value>)',
}
}
}
// Usage - no dark: prefix needed
<div className="bg-background text-foreground" />
<div className="
bg-white text-gray-900
dark:bg-gray-900 dark:text-white
" />
const buttonVariants = {
base: "inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none",
variants: {
primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500",
outline: "border border-gray-300 bg-transparent hover:bg-gray-50 focus:ring-gray-500",
ghost: "bg-transparent hover:bg-gray-100 focus:ring-gray-500",
destructive: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",
},
sizes: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base",
}
};
<div className="rounded-lg border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-950">
<div className="p-6">
<h3 className="text-lg font-semibold">Title</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">Description</p>
</div>
</div>
<input className="
flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2
text-sm placeholder:text-gray-400
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
disabled:cursor-not-allowed disabled:opacity-50
dark:border-gray-700 dark:bg-gray-900
" />
gap-1 / p-1 = 4px (tight)gap-2 / p-2 = 8px (compact)gap-4 / p-4 = 16px (default)gap-6 / p-6 = 24px (comfortable)gap-8 / p-8 = 32px (spacious)<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Content */}
</div>
// Smooth color/opacity transitions
<button className="transition-colors duration-200" />
// Transform transitions (scale, translate)
<div className="transition-transform duration-200 hover:scale-105" />
// All properties
<div className="transition-all duration-300" />
<div className="animate-spin" /> // Loading spinner
<div className="animate-pulse" /> // Skeleton loading
<div className="animate-bounce" /> // Attention grabber
<div className="animate-ping" /> // Notification dot
// Always visible focus rings
<button className="
focus:outline-none
focus:ring-2
focus:ring-blue-500
focus:ring-offset-2
" />
// Focus-visible for keyboard only
<button className="
focus-visible:outline-none
focus-visible:ring-2
focus-visible:ring-blue-500
" />
<span className="sr-only">Loading...</span> // Hidden visually, announced
<div aria-hidden="true" className="..." /> // Decorative, ignored
// 12-column grid
<div className="grid grid-cols-12 gap-4">
<div className="col-span-8">Main content</div>
<div className="col-span-4">Sidebar</div>
</div>
// Auto-fit responsive grid
<div className="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] gap-6">
{items.map(item => <Card key={item.id} />)}
</div>
// Dashboard grid
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard />
<StatCard />
<StatCard />
<StatCard />
</div>
// Fixed sidebar + scrollable content
<div className="flex h-screen">
<aside className="w-64 flex-shrink-0 border-r bg-gray-50 overflow-y-auto">
<nav className="p-4">{/* Nav items */}</nav>
</aside>
<main className="flex-1 overflow-y-auto p-6">
{/* Main content */}
</main>
</div>
// Collapsible sidebar
<div className="flex h-screen">
<aside className={cn(
"flex-shrink-0 border-r bg-gray-50 transition-all duration-300",
isOpen ? "w-64" : "w-16"
)}>
{/* Sidebar content */}
</aside>
<main className="flex-1">{/* Content */}</main>
</div>
// Stacked form
<form className="space-y-4 max-w-md">
<div>
<label className="block text-sm font-medium mb-1">Email</label>
<input className="w-full rounded-md border px-3 py-2" />
</div>
<div>
<label className="block text-sm font-medium mb-1">Password</label>
<input className="w-full rounded-md border px-3 py-2" type="password" />
</div>
<button className="w-full bg-blue-600 text-white rounded-md py-2">
Sign In
</button>
</form>
// Inline form (search bar)
<form className="flex gap-2">
<input className="flex-1 rounded-md border px-3 py-2" placeholder="Search..." />
<button className="px-4 py-2 bg-blue-600 text-white rounded-md">Search</button>
</form>
// Two-column form
<form className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1">First Name</label>
<input className="w-full rounded-md border px-3 py-2" />
</div>
<div>
<label className="block text-sm font-medium mb-1">Last Name</label>
<input className="w-full rounded-md border px-3 py-2" />
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium mb-1">Email</label>
<input className="w-full rounded-md border px-3 py-2" />
</div>
</form>
// Sticky header + footer
<div className="min-h-screen flex flex-col">
<header className="sticky top-0 z-50 border-b bg-white">
<nav className="mx-auto max-w-7xl px-4 h-16 flex items-center justify-between">
{/* Nav content */}
</nav>
</header>
<main className="flex-1">
{/* Page content */}
</main>
<footer className="border-t py-8">
{/* Footer content */}
</footer>
</div>
// Horizontally centered with max-width
<div className="mx-auto max-w-4xl px-4">{/* Content */}</div>
// Vertically and horizontally centered
<div className="min-h-screen flex items-center justify-center">
<div className="w-full max-w-md">{/* Centered card */}</div>
</div>
@apply excessively - defeats Tailwind's purpose[123px] when scale values existSearch for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer