Tailwind CSS has revolutionized how we write CSS. This article explores best practices and advanced techniques.
Tailwind Core Concepts
Utility-First CSS
Tailwind Design Philosophy:
┌─────────────────────────────────────────────────────┐
│ │
│ Traditional CSS │
│ └── Write semantic class names → Define styles │
│ │
│ Tailwind CSS │
│ └── Compose utility classes → Build in HTML │
│ │
│ Benefits │
│ ├── No naming struggles │
│ ├── Styles as components │
│ ├── Consistent design system │
│ └── Smaller production bundle │
│ │
└─────────────────────────────────────────────────────┘
Basic Usage
<!-- Traditional approach -->
<div class="card">
<h2 class="card-title">Title</h2>
<p class="card-content">Content</p>
</div>
<!-- Tailwind approach -->
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-bold text-gray-900 mb-2">Title</h2>
<p class="text-gray-600">Content</p>
</div>
Configuration
tailwind.config.js
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx,astro}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
// Custom colors
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
},
brand: '#ff6b35',
},
// Custom fonts
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
// Custom spacing
spacing: {
'18': '4.5rem',
'88': '22rem',
'128': '32rem',
},
// Custom breakpoints
screens: {
'xs': '475px',
'3xl': '1920px',
},
// Custom animations
animation: {
'fade-in': 'fadeIn 0.5s ease-out',
'slide-up': 'slideUp 0.3s ease-out',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { transform: 'translateY(10px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
],
};
Responsive Design
Breakpoint Usage
<!-- Mobile-first design -->
<div class="
w-full /* Default: full width */
md:w-1/2 /* Medium screens: half */
lg:w-1/3 /* Large screens: third */
xl:w-1/4 /* Extra large: quarter */
">
Responsive Card
</div>
<!-- Responsive grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
<div>Item 4</div>
</div>
<!-- Responsive navigation -->
<nav class="flex flex-col md:flex-row md:items-center md:justify-between">
<div class="text-xl font-bold">Logo</div>
<div class="hidden md:flex space-x-4">
<a href="#">Home</a>
<a href="#">About</a>
<a href="#">Contact</a>
</div>
</nav>
Container Queries
<!-- Using @container -->
<div class="@container">
<div class="@md:flex @md:items-center">
<img class="w-full @md:w-48" src="..." alt="" />
<div class="@md:ml-4">
<h3 class="text-lg @lg:text-xl">Title</h3>
</div>
</div>
</div>
State Variants
Interactive States
<!-- Hover, Focus, Active -->
<button class="
bg-blue-500
hover:bg-blue-600
focus:ring-2
focus:ring-blue-300
active:bg-blue-700
transition-colors
">
Button
</button>
<!-- Group Hover -->
<div class="group">
<div class="group-hover:bg-gray-100 p-4">
<h3 class="group-hover:text-blue-600">Title</h3>
<p class="group-hover:text-gray-700">Description</p>
</div>
</div>
<!-- Peer State -->
<input type="checkbox" class="peer" />
<label class="peer-checked:text-blue-600">
Turns blue when checked
</label>
Dark Mode
<!-- System preference -->
<div class="bg-white dark:bg-gray-900">
<h1 class="text-gray-900 dark:text-white">Title</h1>
<p class="text-gray-600 dark:text-gray-400">Content</p>
</div>
<!-- Manual toggle -->
<script>
// tailwind.config.js: darkMode: 'class'
function toggleDark() {
document.documentElement.classList.toggle('dark');
}
</script>
Component Abstraction
@apply Directive
/* styles/components.css */
@layer components {
.btn {
@apply px-4 py-2 rounded-lg font-medium transition-colors;
}
.btn-primary {
@apply btn bg-blue-500 text-white hover:bg-blue-600;
}
.btn-secondary {
@apply btn bg-gray-200 text-gray-800 hover:bg-gray-300;
}
.input {
@apply w-full px-3 py-2 border border-gray-300 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent;
}
.card {
@apply bg-white rounded-xl shadow-lg p-6;
}
}
React Components
// components/Button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-lg font-medium transition-colors',
{
variants: {
variant: {
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
outline: 'border border-gray-300 hover:bg-gray-50',
ghost: 'hover:bg-gray-100',
},
size: {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2',
lg: 'px-6 py-3 text-lg',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
export function Button({ className, variant, size, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
);
}
// Usage
<Button variant="primary" size="lg">Primary</Button>
<Button variant="outline">Outline</Button>
cn Utility Function
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Usage
cn('px-4 py-2', isActive && 'bg-blue-500', className);
// Automatically merges conflicting classes
Layout Patterns
Flexbox
<!-- Horizontal center -->
<div class="flex justify-center">
<div>Centered content</div>
</div>
<!-- Vertical center -->
<div class="flex items-center h-screen">
<div>Centered content</div>
</div>
<!-- Full center -->
<div class="flex items-center justify-center h-screen">
<div>Centered content</div>
</div>
<!-- Space Between -->
<div class="flex justify-between items-center">
<div>Left</div>
<div>Right</div>
</div>
Grid
<!-- Basic grid -->
<div class="grid grid-cols-3 gap-4">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<!-- Auto-fit grid -->
<div class="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
<div>Card 1</div>
<div>Card 2</div>
<div>Card 3</div>
</div>
<!-- Complex layout -->
<div class="grid grid-cols-12 gap-4">
<div class="col-span-12 lg:col-span-8">Main</div>
<div class="col-span-12 lg:col-span-4">Sidebar</div>
</div>
Animations
<!-- Transitions -->
<button class="
bg-blue-500
hover:bg-blue-600
transition-all
duration-300
ease-in-out
hover:scale-105
">
Hover to scale
</button>
<!-- Custom animations -->
<div class="animate-fade-in">
Fade in element
</div>
<div class="animate-pulse">
Pulse effect
</div>
<div class="animate-spin">
Spinning loader
</div>
<!-- Conditional animation -->
<div class="
opacity-0
translate-y-4
transition-all
duration-500
data-[visible=true]:opacity-100
data-[visible=true]:translate-y-0
">
Scroll reveal
</div>
Performance
Production Build
// tailwind.config.js
module.exports = {
content: [
// Only scan needed files
'./src/**/*.{js,ts,jsx,tsx}',
],
// Automatic Tree Shaking in production
};
Minimizing Classes
<!-- Avoid repetition -->
<div class="p-4 m-4">
<!-- Use space instead of multiple margins -->
<div class="space-y-4">
<div>Item 1</div>
<div>Item 2</div>
</div>
</div>
<!-- Use divide instead of borders -->
<ul class="divide-y divide-gray-200">
<li class="py-4">Item 1</li>
<li class="py-4">Item 2</li>
</ul>
Best Practices Summary
Tailwind CSS Best Practices:
┌─────────────────────────────────────────────────────┐
│ Design System │
│ ├── Define design tokens in config │
│ ├── Use consistent spacing and colors │
│ ├── Create reusable components │
│ └── Use CVA for variants │
│ │
│ Code Organization │
│ ├── Group classes logically │
│ ├── Use @apply for common patterns │
│ ├── Use cn() for class merging │
│ └── Componentize long class lists │
│ │
│ Performance │
│ ├── Configure content paths correctly │
│ ├── Automatic purge in production │
│ ├── Avoid dynamic class concatenation │
│ └── Use JIT mode │
│ │
│ Responsive Design │
│ ├── Mobile-first approach │
│ ├── Use breakpoints wisely │
│ ├── Container queries │
│ └── Dark mode support │
└─────────────────────────────────────────────────────┘
| Scenario | Recommended Approach |
|---|---|
| Button components | CVA + cn() |
| Form styles | @tailwindcss/forms |
| Article typography | @tailwindcss/typography |
| Dark mode | dark: prefix |
Tailwind makes styles part of components. No more CSS naming struggles.