--- name: tailwind description: Use when composing Tailwind utilities, building responsive or dark-mode layouts, defining component variants with cva, resolving conflicting classes with tailwind-merge, or configuring a custom theme. --- # Tailwind CSS Patterns Utility-first CSS with Tailwind v3/v4, component variants, and the cn() pattern. ## When to Activate - Composing utilities for layout (flex, grid, spacing, sizing) - Responsive design with breakpoint prefixes - Dark mode with `dark:` variants - Building component variants with `cva` (class-variance-authority) - Deduplicating conflicting classes with `tailwind-merge` - Configuring custom colors, fonts, or spacing in `tailwind.config` - Animating with `transition`, `animate-*`, or custom keyframes --- ## Core Utility Patterns ### Spacing and sizing ```tsx // Padding / margin — p-{n}, m-{n}, px-{n}, py-{n}, pt/pr/pb/pl
{/* p=16px, mt=8px, px=24px, py=12px */} // Width / height
{/* arbitrary values */} // Margin auto (centering)
``` ### Flexbox ```tsx
// Flex children
{/* grow, allow shrinking below content size */}
{/* fixed width */}
{/* don't shrink */} ``` ### Grid ```tsx
{/* arbitrary template */}
{/* Spanning */}
``` ### Typography ```tsx

// Truncate long text

{/* single line ellipsis */}

{/* clamp to 3 lines */} ``` --- ## Responsive Design Tailwind is **mobile-first** — unprefixed utilities apply to all sizes, prefixes override at that breakpoint and up. ```tsx // Breakpoints: sm(640px), md(768px), lg(1024px), xl(1280px), 2xl(1536px)

// Hide/show
{/* hidden on mobile, visible md+ */}
{/* visible on mobile, hidden md+ */} // Text size changes per breakpoint

``` --- ## Dark Mode ```tsx // tailwind.config — use 'class' strategy (toggle via class on ) darkMode: 'class' // Next.js — use next-themes // In components
``` --- ## Custom Theme Config ```js // tailwind.config.ts import type { Config } from "tailwindcss" export default { content: ["./src/**/*.{ts,tsx}"], darkMode: "class", theme: { extend: { colors: { brand: { 50: "#eff6ff", 500: "#3b82f6", 900: "#1e3a5f", }, // CSS variable-based (works with shadcn/ui) background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, }, fontFamily: { sans: ["var(--font-inter)", "sans-serif"], mono: ["var(--font-mono)", "monospace"], }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", }, keyframes: { "fade-in": { from: { opacity: "0", transform: "translateY(4px)" }, to: { opacity: "1", transform: "translateY(0)" }, }, "spin-slow": { to: { transform: "rotate(360deg)" }, }, }, animation: { "fade-in": "fade-in 0.2s ease-out", "spin-slow": "spin-slow 3s linear infinite", }, }, }, } satisfies Config ``` --- ## Animations and Transitions ```tsx // Transitions — apply to base element, variants add what changes

``` ### Input ```tsx ``` ### Badge ```tsx const badgeVariants = cva( "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium", { variants: { variant: { default: "bg-gray-100 text-gray-800", success: "bg-green-100 text-green-800", warning: "bg-yellow-100 text-yellow-800", error: "bg-red-100 text-red-800", info: "bg-blue-100 text-blue-800", }, }, defaultVariants: { variant: "default" }, }, ) ``` ### Modal overlay ```tsx {/* Backdrop */}
{/* Panel */}
...
``` --- ## Tailwind v4 (New in 2025) ```css /* No tailwind.config — configure in CSS */ @import "tailwindcss"; @theme { --color-brand-500: #3b82f6; --font-sans: "Inter", sans-serif; --radius-lg: 0.75rem; } /* Dark mode via media query by default (no class strategy needed) */ /* Use @variant dark { ... } for custom dark styles */ ``` v4 changes: config moves to CSS `@theme`, faster build, no PostCSS required, native cascade layers. --- ## Red Flags - **String template literals for conditional classes** — `` `bg-${color} p-4` `` generates class names at runtime that Tailwind's static scanner never sees and therefore never includes in the output CSS; use only complete class names from the source, with `cn()` for conditions - **Conflicting utilities without `tailwind-merge`** — `className="px-4 px-6"` applies both; the last rule in the stylesheet wins (not the last in the string), which is unpredictable; `twMerge` resolves conflicts correctly by keeping only the last conflicting utility - **Repeating variant logic in `if/else` strings** — `className={isActive ? "bg-blue-600 text-white rounded-lg px-4" : "bg-gray-100 text-gray-900 rounded-lg px-4"}` duplicates base classes; use `cva` to declare base + variant styles separately - **Arbitrary values that are repeated** — `w-[340px]` appearing in five components belongs in `theme.extend` as a named token; arbitrary values are for one-offs only - **Missing `dark:` variants on color utilities** — adding `bg-white text-gray-900` without corresponding `dark:bg-gray-900 dark:text-gray-100` makes dark mode look broken; pair every color with its dark-mode variant at the same time - **No `focus-visible:ring` on interactive elements** — keyboard users navigating with Tab have no visible indicator when focus styles are absent; every button and link needs a `focus-visible:ring-*` class for WCAG AA compliance - **Importing Tailwind classes from JS variables** — storing class names in a variable and spreading them defeats the static content scanner; Tailwind purges any class it cannot find as a complete string in the source at build time ## Checklist - [ ] `cn()` used instead of string concatenation for conditional classes - [ ] `cva` used for components with multiple variants (not `if/else` strings) - [ ] Responsive classes ordered mobile-first (base → sm: → md: → lg:) - [ ] `group` / `peer` used for parent/sibling state instead of JS state - [ ] `dark:` variants added alongside every color utility - [ ] `transition-colors` / `transition-all` on interactive elements - [ ] `focus-visible:ring` on all interactive elements for keyboard accessibility - [ ] Arbitrary values (`w-[340px]`) used sparingly — extend theme for repeated values - [ ] `disabled:` variants on form elements to style disabled state