tailwind-css
Tailwind CSS 4.x
Platform: Web (Tailwind CSS 4.x). For mobile styling, see the nativewind skill (Tailwind 3.x for React Native).
Overview
Utility-first CSS framework for rapid UI development. Tailwind CSS 4.x introduces a CSS-first configuration approach, automatic content detection, and improved performance with a new engine.
Install: pnpm add -D tailwindcss @tailwindcss/vite
API Reference: Use Context7 MCP for full utility class reference (
mcp__context7__resolve-library-id→tailwindcss).
Key Changes in v4
- CSS-first configuration - Use
@themein CSS instead oftailwind.config.js - Single import - Use
@import "tailwindcss"instead of separate directives - Automatic content detection - No
contentarray needed - New color system - OKLCH colors with wide gamut support
- Built-in Vite plugin -
@tailwindcss/vitefor optimal integration
Workflows
Setting up Tailwind v4:
- Install dependencies:
pnpm add -D tailwindcss @tailwindcss/vite - Add Vite plugin to
vite.config.ts - Create
index.csswith@import "tailwindcss" - Customize theme with
@themedirective - Test build process and verify styles load
Creating components:
- Start with semantic HTML structure
- Apply utility classes for layout (flex, grid)
- Add spacing utilities (p-, m-, gap-*)
- Style with color, typography, borders
- Add responsive variants (sm:, md:, lg:)
- Test in multiple viewports
Custom theme:
- Define design tokens using
@themein CSS - Add custom colors, spacing, fonts
- Create semantic color aliases
- Verify tokens work across components
Configuration
Vite Configuration
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [react(), tailwindcss()],
});
CSS Entry Point
/* src/index.css */
@import "tailwindcss";
@theme {
/* Brand colors (OKLCH for wide gamut) */
--color-primary: oklch(0.6 0.2 250);
--color-secondary: oklch(0.6 0.15 300);
--color-success: oklch(0.7 0.2 150);
--color-warning: oklch(0.8 0.15 85);
--color-danger: oklch(0.6 0.25 25);
/* Typography */
--font-sans: "Inter", system-ui, sans-serif;
--font-mono: "Fira Code", monospace;
/* Custom spacing */
--spacing-128: 32rem;
/* Custom animations */
--animate-fade-in: fade-in 0.3s ease-out;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* Dark mode variant */
@variant dark (&:where(.dark, .dark *));
Responsive Design
Default breakpoints (mobile-first): sm: 640px | md: 768px | lg: 1024px | xl: 1280px | 2xl: 1536px
// Stack on mobile, grid on desktop
<div className="flex flex-col md:flex-row gap-4">
<div className="w-full md:w-1/2">Left</div>
<div className="w-full md:w-1/2">Right</div>
</div>
// Responsive grid
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{items.map(item => <div key={item.id} className="rounded-lg border p-4">{item.content}</div>)}
</div>
// Responsive typography
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl">Scales with viewport</h1>
// Show/hide at breakpoints
<div className="hidden lg:block">Desktop only</div>
<div className="block lg:hidden">Mobile only</div>
Custom breakpoints: add --breakpoint-demo: 1440px; in @theme.
Dark Mode
function App() {
const [isDark, setIsDark] = useState(false);
useEffect(() => { document.documentElement.classList.toggle('dark', isDark); }, [isDark]);
return (
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<button onClick={() => setIsDark(!isDark)}>Toggle Dark Mode</button>
</div>
);
}
Component Patterns
Buttons
// Primary
<button className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
Primary
</button>
// Secondary
<button className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors">
Secondary
</button>
Cards
// Basic card
<div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
<h3 className="text-lg font-semibold text-gray-900">Card Title</h3>
<p className="mt-2 text-sm text-gray-600">Description</p>
</div>
// Card with header/footer
<div className="rounded-lg border border-gray-200 bg-white overflow-hidden">
<div className="border-b border-gray-200 bg-gray-50 px-6 py-4">
<h3 className="text-lg font-semibold text-gray-900">Header</h3>
</div>
<div className="p-6"><p className="text-sm text-gray-600">Content</p></div>
<div className="border-t border-gray-200 bg-gray-50 px-6 py-4">
<button className="text-sm font-medium text-blue-600 hover:text-blue-700">Action</button>
</div>
</div>
Form Input
<div className="space-y-1">
<label htmlFor="email" className="block text-sm font-medium text-gray-700">Email</label>
<input
type="email"
id="email"
className="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm placeholder-gray-400 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
placeholder="you@example.com"
/>
</div>
Advanced Patterns
Variant Modifiers
// Group hover — child reacts to parent hover
<div className="group hover:bg-gray-100">
<h3 className="group-hover:text-blue-600">Changes on parent hover</h3>
</div>
// Peer — sibling state
<input type="checkbox" className="peer" />
<label className="peer-checked:text-blue-600">Changes when checked</label>
// Data attributes
<div data-state="active" className="data-[state=active]:bg-blue-100">Active state</div>
// Structural: first:, last:, odd:, even:
<li className="first:rounded-t-lg last:rounded-b-lg border-b last:border-b-0">Item</li>
Arbitrary Values and Custom Utilities
// Arbitrary values with []
<div className="top-[117px] bg-[#1da1f2] grid-cols-[1fr_500px_2fr]">Custom values</div>
<div className="w-[calc(100%-2rem)] bg-[var(--brand-color)]">CSS var + calc</div>
/* Custom utilities with @utility */
@utility scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
}
Migration from v3
| v3 Pattern | v4 Pattern |
|---|---|
tailwind.config.js |
@theme in CSS |
@tailwind base; etc. |
@import "tailwindcss" |
content: [...] |
Automatic detection |
| PostCSS plugin | @tailwindcss/vite plugin |
Steps: Install new packages → replace PostCSS config with Vite plugin → replace @tailwind directives → move theme to @theme → remove content array → test.
Best Practices
- Mobile-first approach - Start with base styles, add responsive variants
- Use semantic color names - primary, secondary, danger via
@theme - Keep classes organized - Layout → Spacing → Colors → Typography → States
- Use arbitrary values sparingly - Prefer theme tokens
- Group hover/focus states with group-* and peer-* utilities
- Use the Vite plugin -
@tailwindcss/vitefor best performance
Anti-Patterns
- Using
tailwind.config.jsfor customization (use@theme) - Using
@tailwinddirectives (use@import "tailwindcss") - Manually configuring
contentarray (auto-detected in v4) - Using PostCSS plugin with Vite (use
@tailwindcss/vite) - Desktop-first responsive design (not mobile-friendly)
- Hardcoding colors instead of using theme tokens
- Ignoring accessibility (missing focus states)
Feedback Loops
# Check generated CSS size (target < 50KB)
pnpm build
# Watch for changes
pnpm dev
Test focus states by tabbing through interactive elements. Verify focus:ring-* utilities are visible. Check color contrast in browser devtools.