Tailwind CSS: Building Reusable Components
Configuration and Design Tokens
// tailwind.config.js
module.exports = {
content: ['./src/**/*.{ts,tsx,html}'],
theme: {
extend: {
colors: {
brand: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
900: '#1e3a8a',
},
danger: '#ef4444',
success: '#22c55e',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
spacing: {
18: '4.5rem',
88: '22rem',
},
animation: {
'slide-in': 'slideIn 0.2s ease-out',
},
keyframes: {
slideIn: {
'0%': { transform: 'translateY(-10px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
};
Component Variants with cva
import { cva, type VariantProps } from 'class-variance-authority';
const button = cva(
// Base styles
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-brand-600 text-white hover:bg-brand-700 focus-visible:ring-brand-500',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
outline: 'border border-gray-300 bg-transparent hover:bg-gray-50',
ghost: 'hover:bg-gray-100',
danger: 'bg-danger text-white hover:bg-red-600',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4',
lg: 'h-12 px-6 text-lg',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof button> {
loading?: boolean;
}
export function Button({ variant, size, className, loading, children, ...props }: ButtonProps) {
return (
<button
className={button({ variant, size, className })}
disabled={loading || props.disabled}
{...props}
>
{loading && <Spinner className="mr-2 h-4 w-4" />}
{children}
</button>
);
}
Dark Mode
// tailwind.config.js
module.exports = {
darkMode: 'class', // or 'media' for OS preference
};
// Theme toggle
import { useState, useEffect } from 'react';
function useTheme() {
const [theme, setTheme] = useState<'light' | 'dark'>(() => {
if (typeof window === 'undefined') return 'light';
return (localStorage.getItem('theme') as 'light' | 'dark') ?? 'light';
});
useEffect(() => {
document.documentElement.classList.toggle('dark', theme === 'dark');
localStorage.setItem('theme', theme);
}, [theme]);
return { theme, toggle: () => setTheme(t => t === 'light' ? 'dark' : 'light') };
}
// Usage in components
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Hello</h1>
</div>
Responsive Design Patterns
<!-- Mobile-first responsive grid -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<!-- Cards -->
</div>
<!-- Responsive sidebar layout -->
<div class="flex flex-col lg:flex-row gap-6">
<aside class="w-full lg:w-64 shrink-0">Sidebar</aside>
<main class="flex-1 min-w-0">Main content</main>
</div>
<!-- Hide/show at breakpoints -->
<nav class="hidden md:flex gap-4">Desktop nav</nav>
<button class="md:hidden">Hamburger</button>
Typography Plugin
<!-- Rich text content with prose classes -->
<article class="prose prose-gray dark:prose-invert max-w-none lg:prose-xl">
<!-- Markdown-rendered HTML -->
</article>
Tailwind's utility-first approach eliminates naming friction and enables rapid prototyping.