Tailwind CSS Expert
Constraints are the feature: style from the theme scale, not magic numbers. Composition + variants beat @apply. If a class string is unreadable, extract a component or a cva variant — not a custom CSS file.
When to Use
- Styling components with Tailwind or migrating hand-written CSS → Tailwind.
- Responsive, dark-mode, or themeable layouts.
- Taming long/conditional class lists; building reusable styled primitives.
- Configuring the theme (colors, spacing, breakpoints, plugins).
When NOT to Use
- Component state/logic →
react-expert.
- Accessibility semantics beyond focus/contrast (ARIA, keyboard) →
accessibility-expert.
Core Principles
1. Stay on the scale
- Use theme tokens (
p-4, gap-2, text-lg, text-gray-600) over arbitrary values (p-[13px]). Arbitrary values are an escape hatch, not the norm.
- Define brand colors/spacing/typography in the theme (Tailwind v4:
@theme in CSS; v3: tailwind.config) so every utility stays consistent. Know your version — v4 is CSS-first and config is largely in CSS.
2. Manage class complexity by composition
- Repeated UI → a component, not copy-pasted class strings.
@apply is only for tiny shared primitives, not whole components.
- Conditional/variant styling →
clsx for booleans, cva (class-variance-authority) for structured variants. Merge conflicting classes with tailwind-merge.
3. Mobile-first, state-aware
- Unprefixed = base (smallest screen); layer up with
sm:/md:/lg:. Never "undo" a larger breakpoint with a smaller one.
- Use state variants:
hover:, focus-visible:, active:, disabled:, aria-*:, data-*:, group-hover:, peer-checked:, dark:.
4. Layout with flor/grid, theme with tokens
- Space items with
flex/grid + gap-* rather than per-item margins.
- Dark mode: prefer
dark: driven by a class strategy you toggle, so it's controllable and testable.
Decision Guide
| Situation |
Use |
| One-off conditional classes |
clsx |
| Component with size/intent variants |
cva + tailwind-merge |
Tiny shared primitive (e.g., .btn-base) |
@apply |
| Truly bespoke value not in scale |
arbitrary [...] (sparingly) |
| Spacing between siblings |
gap-* on a flex/grid parent |
Common Mistakes
@apply everywhere → recreates the CSS-soup Tailwind avoids and loses co-location. Compose components instead.
- Arbitrary values as default (
mt-[7px]) → inconsistent spacing; use the scale.
- Conditionally concatenating strings → conflicting classes ("last wins" is unreliable); use
tailwind-merge.
- Dynamic class names built from variables (
`text-${color}-500`) → purged away by the compiler; use full static class names or a lookup map.
- Fighting specificity → don't mix ad-hoc CSS; resolve within utilities.
- Index/order assumptions → utility order in the class attribute doesn't set precedence; the generated CSS order does.
Examples
Responsive, dark-mode card (tokens + states)
<article class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm
transition hover:shadow-md
dark:border-gray-800 dark:bg-gray-900
sm:p-6 md:flex md:items-center md:gap-6">
<img class="h-16 w-16 rounded-full object-cover" src="/avatar.jpg" alt="" />
<div class="mt-3 md:mt-0">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Title</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Subtitle</p>
</div>
</article>
Type-safe variants with cva + safe merge
import { cva, type VariantProps } from "class-variance-authority";
import { twMerge } from "tailwind-merge";
export const button = cva(
"inline-flex items-center rounded-md px-3 py-2 text-sm font-medium focus-visible:ring-2 disabled:opacity-50",
{
variants: {
intent: { primary: "bg-blue-600 text-white hover:bg-blue-700",
ghost: "bg-transparent text-blue-600 hover:bg-blue-50" },
size: { sm: "text-xs px-2 py-1", md: "text-sm" },
},
defaultVariants: { intent: "primary", size: "md" },
},
);
export type ButtonProps = VariantProps<typeof button>;
// usage: className={twMerge(button({ intent: "ghost" }), className)}
See Also
react-expert — applying classes and variants in components.
accessibility-expert — focus-visible states and contrast ratios.
nextjs-expert — styling App Router apps and shared layouts.
1---2name: tailwind-expert3description: Expert Tailwind CSS: utility-first styling, responsive/dark mode, theming, and design systems. Trigger keywords: Tailwind, CSS, utility classes, responsive, breakpoints, dark mode, design tokens, theme, @apply, cva, clsx, class-variance-authority, arbitrary values, shadcn. Use for styling UIs, responsive layouts, theming, or untangling class soup.4---56# Tailwind CSS Expert78> Constraints are the feature: style from the theme scale, not magic numbers. Composition + variants beat `@apply`. If a class string is unreadable, extract a component or a `cva` variant — not a custom CSS file.910## When to Use11- Styling components with Tailwind or migrating hand-written CSS → Tailwind.12- Responsive, dark-mode, or themeable layouts.13- Taming long/conditional class lists; building reusable styled primitives.14- Configuring the theme (colors, spacing, breakpoints, plugins).1516## When NOT to Use17- Component state/logic → `react-expert`.18- Accessibility semantics beyond focus/contrast (ARIA, keyboard) → `accessibility-expert`.1920## Core Principles2122### 1. Stay on the scale23- Use theme tokens (`p-4`, `gap-2`, `text-lg`, `text-gray-600`) over arbitrary values (`p-[13px]`). Arbitrary values are an escape hatch, not the norm.24- Define brand colors/spacing/typography in the theme (Tailwind v4: `@theme` in CSS; v3: `tailwind.config`) so every utility stays consistent. Know your version — v4 is CSS-first and config is largely in CSS.2526### 2. Manage class complexity by composition27- Repeated UI → a **component**, not copy-pasted class strings. `@apply` is only for tiny shared primitives, not whole components.28- Conditional/variant styling → `clsx` for booleans, `cva` (class-variance-authority) for structured variants. Merge conflicting classes with `tailwind-merge`.2930### 3. Mobile-first, state-aware31- Unprefixed = base (smallest screen); layer up with `sm:`/`md:`/`lg:`. Never "undo" a larger breakpoint with a smaller one.32- Use state variants: `hover:`, `focus-visible:`, `active:`, `disabled:`, `aria-*:`, `data-*:`, `group-hover:`, `peer-checked:`, `dark:`.3334### 4. Layout with flor/grid, theme with tokens35- Space items with `flex`/`grid` + `gap-*` rather than per-item margins.36- Dark mode: prefer `dark:` driven by a `class` strategy you toggle, so it's controllable and testable.3738## Decision Guide39| Situation | Use |40|-----------|-----|41| One-off conditional classes | `clsx` |42| Component with size/intent variants | `cva` + `tailwind-merge` |43| Tiny shared primitive (e.g., `.btn-base`) | `@apply` |44| Truly bespoke value not in scale | arbitrary `[...]` (sparingly) |45| Spacing between siblings | `gap-*` on a flex/grid parent |4647## Common Mistakes48- **`@apply` everywhere** → recreates the CSS-soup Tailwind avoids and loses co-location. Compose components instead.49- **Arbitrary values as default** (`mt-[7px]`) → inconsistent spacing; use the scale.50- **Conditionally concatenating strings** → conflicting classes ("last wins" is unreliable); use `tailwind-merge`.51- **Dynamic class names built from variables** (`` `text-${color}-500` ``) → purged away by the compiler; use full static class names or a lookup map.52- **Fighting specificity** → don't mix ad-hoc CSS; resolve within utilities.53- **Index/order assumptions** → utility order in the class attribute doesn't set precedence; the generated CSS order does.5455## Examples5657**Responsive, dark-mode card (tokens + states)**58```html59<article class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm60 transition hover:shadow-md61 dark:border-gray-800 dark:bg-gray-90062 sm:p-6 md:flex md:items-center md:gap-6">63 <img class="h-16 w-16 rounded-full object-cover" src="/avatar.jpg" alt="" />64 <div class="mt-3 md:mt-0">65 <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Title</h3>66 <p class="text-sm text-gray-600 dark:text-gray-400">Subtitle</p>67 </div>68</article>69```7071**Type-safe variants with cva + safe merge**72```ts73import { cva, type VariantProps } from "class-variance-authority";74import { twMerge } from "tailwind-merge";7576export const button = cva(77 "inline-flex items-center rounded-md px-3 py-2 text-sm font-medium focus-visible:ring-2 disabled:opacity-50",78 {79 variants: {80 intent: { primary: "bg-blue-600 text-white hover:bg-blue-700",81 ghost: "bg-transparent text-blue-600 hover:bg-blue-50" },82 size: { sm: "text-xs px-2 py-1", md: "text-sm" },83 },84 defaultVariants: { intent: "primary", size: "md" },85 },86);87export type ButtonProps = VariantProps<typeof button>;88// usage: className={twMerge(button({ intent: "ghost" }), className)}89```9091## See Also92- `react-expert` — applying classes and variants in components.93- `accessibility-expert` — focus-visible states and contrast ratios.94- `nextjs-expert` — styling App Router apps and shared layouts.