| name | tailwind |
| description | Tailwind CSS v4 best practices, CSS-first configuration and modern patterns. Use when writing styles, configuring themes or migrating from v3. |
Tailwind CSS v4 Best Practices
When to Apply
Use this skill when:
- Writing or refactoring component styles
- Configuring themes and design tokens
- Migrating from Tailwind v3 to v4
- Working with dark mode, responsive design, or animations
- Deciding between utility classes and custom CSS
Core Principles
1. CSS-First Configuration
Tailwind v4 eliminates tailwind.config.js in favor of native CSS.
Do:
- Use
@theme {}for design tokens - Define custom values directly in CSS
- Keep configuration co-located with styles
Don't:
- Create a
tailwind.config.js(not needed in v4) - Use JavaScript for simple theme customization
/* index.css */
@import "tailwindcss";
@theme {
--color-brand: #3b82f6;
--color-brand-dark: #1d4ed8;
--font-display: "Inter", sans-serif;
--spacing-128: 32rem;
--animate-slide-in: slide-in 0.3s ease-out;
}
2. Use cn() for Class Merging
Use the cn utility (clsx + tailwind-merge) for conditional classes and prop overrides.
Do:
- Use
cn()when classes are conditional or come from props - Let tailwind-merge resolve conflicts automatically
Don't:
- Use template literals for conditional classes
- Manually handle class conflicts
import { cn } from "../utils/cn";
// Conditional classes
<div className={cn("p-4 rounded-lg", isActive && "bg-blue-500", className)}>
// Resolves conflicts: last value wins
cn("px-4 py-2", "p-6") // → "p-6"
cn("text-red-500", "text-blue-500") // → "text-blue-500"
Setup & Configuration
Basic Setup (Vite)
/* index.css */
@import "tailwindcss";
// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [tailwindcss()],
});
Theme Configuration
All design tokens use CSS custom properties with specific prefixes:
@theme {
/* Colors: --color-* */
--color-primary: #3b82f6;
--color-primary-hover: #2563eb;
/* Spacing: --spacing-* */
--spacing-18: 4.5rem;
/* Font families: --font-* */
--font-sans: "Inter", system-ui, sans-serif;
--font-mono: "Fira Code", monospace;
/* Font sizes: --text-* */
--text-tiny: 0.625rem;
--text-tiny--line-height: 1rem;
/* Border radius: --radius-* */
--radius-pill: 9999px;
/* Shadows: --shadow-* */
--shadow-soft: 0 2px 8px rgb(0 0 0 / 0.1);
/* Animations: --animate-* */
--animate-fade: fade 0.2s ease-out;
}
@keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}
New v4 Features
Native Cascade Layers
Tailwind v4 uses CSS @layer automatically:
@layer base {
/* Resets and base styles */
html { @apply antialiased; }
}
@layer components {
/* Reusable component classes */
.btn { @apply px-4 py-2 rounded-lg font-medium; }
}
@layer utilities {
/* Custom utilities */
.text-balance { text-wrap: balance; }
}
Container Queries
Built-in support for container queries:
<div className="@container">
<div className="@sm:flex @lg:grid @lg:grid-cols-2">
{/* Responds to container size, not viewport */}
</div>
</div>
3D Transforms
New 3D transform utilities:
<div className="rotate-x-45 rotate-y-12 perspective-500">
{/* 3D transformed element */}
</div>
Wide Gamut Colors (P3)
Use P3 colors for vivid displays:
@theme {
--color-vivid-blue: oklch(0.6 0.25 250);
--color-vivid-pink: oklch(0.7 0.3 350);
}
Subgrid Support
<div className="grid grid-cols-3">
<div className="col-span-2 grid grid-cols-subgrid">
{/* Inherits parent grid columns */}
</div>
</div>
Dark Mode
Automatic Dark Mode
Use the dark: variant:
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
Content adapts to color scheme
</div>
Custom Dark Mode Tokens
@theme {
--color-surface: #ffffff;
--color-surface-dark: #1a1a1a;
}
<div className="bg-[--color-surface] dark:bg-[--color-surface-dark]">
Media Query Strategy (Default)
/* Respects prefers-color-scheme automatically */
@import "tailwindcss";
Class Strategy
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
Responsive Design
Breakpoint System
Default breakpoints (mobile-first):
| Prefix | Min-width | Common use |
|---|---|---|
sm: |
640px | Large phones |
md: |
768px | Tablets |
lg: |
1024px | Laptops |
xl: |
1280px | Desktops |
2xl: |
1536px | Large screens |
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{/* 1 col -> 2 cols -> 3 cols */}
</div>
Custom Breakpoints
@theme {
--breakpoint-xs: 475px;
--breakpoint-3xl: 1920px;
}
Animations
Define Custom Animations
@theme {
--animate-slide-up: slide-up 0.3s ease-out;
--animate-bounce-in: bounce-in 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
@keyframes slide-up {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes bounce-in {
0% { opacity: 0; transform: scale(0.3); }
50% { transform: scale(1.05); }
100% { opacity: 1; transform: scale(1); }
}
Use Animations
<div className="animate-slide-up">Slides up on mount</div>
<div className="animate-bounce-in">Bounces in</div>
Arbitrary Values
Inline Custom Values
{/* One-off values */}
<div className="w-[50vw] h-[calc(100vh-4rem)]">
<div className="bg-[#1a1a2e] text-[--brand-color]">
<div className="grid-cols-[1fr_auto_2fr]">
Arbitrary Variants
{/* Custom selectors */}
<div className="[&>*:first-child]:mt-0">
<div className="[&_.title]:text-lg">
<div className="[@supports(backdrop-filter:blur())]:backdrop-blur">
Best Practices
1. Composition Over Abstraction
Prefer composing utilities over creating component classes:
// Good: Compose in JSX
<button className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
// Only abstract if truly reused many times
2. Use Design Tokens for Brand Values
@theme {
--color-brand-primary: #3b82f6;
--color-brand-secondary: #8b5cf6;
}
<button className="bg-brand-primary hover:bg-brand-secondary">
3. Semantic Color Names
@theme {
/* Semantic names over raw colors */
--color-success: #22c55e;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #3b82f6;
}
4. Group Related Styles
{/* Group by concern for readability */}
<div className="
flex items-center gap-4
p-4 rounded-lg
bg-white dark:bg-gray-800
shadow-sm hover:shadow-md
transition-shadow duration-200
">
5. Use @apply Sparingly
Only use @apply for highly reused patterns:
@layer components {
/* Good: frequently reused base button */
.btn {
@apply px-4 py-2 font-medium rounded-lg transition-colors;
}
}
Code Review Checklist
When reviewing Tailwind code:
- No
tailwind.config.js(use CSS-first config) - Custom values defined in
@theme {} - Using
cn()for conditional/dynamic classes - Using semantic color names
- Responsive styles are mobile-first
- Dark mode properly handled
- Animations defined with
--animate-*tokens -
@applyused sparingly (prefer utilities) - Related utilities grouped together
- No redundant utilities (e.g.,
flex flex-row)