Tailwind CSS v4 Best Practices
Team standard for styling with Tailwind CSS v4 — CSS-first config, design tokens via CSS variables, class-based dark mode.
No Hardcoded Colors (MANDATORY)
Never use raw color values in components. All colors must come from the design token system defined in globals.css.
// BAD: Hardcoded colors
<div className="bg-blue-500 text-white border-gray-200" />
<div className="bg-[#1a73e8] text-[#ffffff]" />
// GOOD: Semantic token colors
<div className="bg-primary text-primary-foreground border-border" />
Before writing any styles: Confirm the color theme with the user. Register all colors in globals.css as CSS variables, then use them everywhere.
No Arbitrary Brackets (MANDATORY)
Tailwind v4 dynamically generates values — stop using [brackets] for things Tailwind already supports natively.
// BAD: Arbitrary brackets
<div className="w-[400px] h-[200px] p-[32px] z-[999] bg-primary/[0.08]" />
// GOOD: Native Tailwind v4 values
<div className="w-100 h-50 p-8 z-999 bg-primary/8" />
How it works in v4:
| Type | Formula | Example |
|---|---|---|
| Spacing (w, h, p, m, gap...) | value × 4px | w-100 = 400px, p-8 = 32px, w-2500 = 10000px |
| Opacity | Integer = percent | bg-primary/4 = 4% opacity, bg-primary/80 = 80% |
| Z-index | Any integer | z-999, z-6969 — no brackets needed |
Rule: Only use [brackets] when there is truly no Tailwind equivalent (e.g., grid-cols-[1fr_2fr]).
Gradients — Use v4 Syntax (MANDATORY)
Tailwind v4 renamed gradients and added radial/conic support. Stop using v3 bg-gradient-to-*.
// BAD: v3 syntax (no longer valid)
<div className="bg-gradient-to-r from-primary to-secondary" />
// GOOD: v4 syntax
<div className="bg-linear-to-r from-primary to-secondary" />
<div className="bg-linear-45 from-primary via-accent to-secondary" />
<div className="bg-radial from-primary to-transparent" />
<div className="bg-conic-180 from-primary via-accent to-secondary" />
| v3 (old) | v4 (new) |
|---|---|
bg-gradient-to-r | bg-linear-to-r |
bg-gradient-to-br | bg-linear-to-br |
| Not available | bg-linear-45 (any angle) |
| Not available | bg-radial |
| Not available | bg-conic-180 (any angle) |
Gradient stops support positions: from-primary from-20% via-accent via-60% to-secondary to-100%
Theming Setup (MANDATORY)
First ask the user: Do you need one theme or two (light + dark)?
Every project must define its color system in globals.css using CSS variables + Tailwind's @theme directive.
Single theme (light only)
If the user only needs one theme, define everything in :root. No .dark, no @custom-variant.
/* globals.css */
@import "tailwindcss";
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
/* ... register all tokens */
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.15 0 0);
--primary: oklch(0.55 0.2 250);
--primary-foreground: oklch(1 0 0);
/* ... see references/theming.md for full token list */
}
Two themes (light + dark)
If the user needs both themes, :root = light, .dark = dark, plus enable class-based toggle.
@import "tailwindcss";
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
/* ... register all tokens */
}
/* Light mode */
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.15 0 0);
--primary: oklch(0.55 0.2 250);
--primary-foreground: oklch(1 0 0);
/* ... */
}
/* Dark mode */
.dark {
--background: oklch(0.12 0 0);
--foreground: oklch(0.95 0 0);
--primary: oklch(0.65 0.2 250);
--primary-foreground: oklch(0.1 0 0);
/* ... */
}
/* Enable class-based dark mode */
@custom-variant dark (&:where(.dark, .dark *));
Then use semantic classes everywhere:
<button class="bg-primary text-primary-foreground hover:bg-primary/90">Save</button>
For full theming guide, see references/theming.md.
Dark Mode (only when two themes are used)
Class-based toggle via .dark on <html>. Semantic tokens handle it automatically.
// Components automatically adapt — no extra work
<div className="bg-background text-foreground" /> {/* Works in light AND dark */}
Never use dark: with hardcoded colors:
// BAD
<div className="bg-white dark:bg-gray-900 text-black dark:text-white" />
// GOOD — tokens handle it
<div className="bg-background text-foreground" />
Component Styling
| Pattern | When to Use |
|---|---|
| Utility classes | Default — most styling |
cn() utility | Conditional/merged classes |
| CSS variables + @theme | Design tokens, theme colors |
@variant | Custom CSS needing Tailwind variants |
@apply | Rarely — only base element resets |
Responsive Design
Mobile-first. Use breakpoint prefixes to scale up:
<div className="px-4 md:px-8 lg:px-12" />
<h1 className="text-2xl md:text-4xl lg:text-5xl" />
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3" />
Always ascending order: base → sm: → md: → lg: → xl: → 2xl:
Tailwind v4 also provides max-* and not-* variants:
<div class="max-lg:p-8">Below lg only</div>
<div class="not-lg:bg-red-500">When NOT at lg or above</div>
<div class="md:max-xl:flex">Range: md to xl only</div>
Animation
Define custom animations in globals.css via @theme + @keyframes:
@theme {
--animate-fade-in: fade-in 0.3s ease-out;
--animate-slide-up: slide-up 0.3s ease-out;
}
<div class="animate-fade-in">Fades in</div>
Always respect reduced motion: motion-safe:animate-fade-in
For full responsive + animation guide, see references/responsive-animation.md.
Code Review
Quick checks for Tailwind code:
- No hardcoded colors? (
bg-blue-500,text-[#fff]) - Semantic tokens used? (
bg-primary,text-foreground) cn()for conditional classes?- Focus styles on all interactive elements?
- Mobile-first responsive? Breakpoints ascending?
- Reduced motion respected?
For full checklist, see references/code-review-checklist.md.