Verify before implementing: For v4-specific syntax (@theme, @variant, CSS-first config), look up current docs via Context7 (query-docs) before writing code. Tailwind v4 changed significantly from v3 and training data may be stale.
Working rules
- Keep utility names as complete source literals and confirm their files are scanned.
- Use shared design tokens and one consistent visibility mechanism.
- Inspect generated CSS after source/configuration changes; JSX strings alone do not prove utilities exist.
- Test scrolling, overlays, and focus behavior in a browser when layout containment changes.
Coding Rules
gapoverspace-x/space-y-- gap handles wrapping; space-* breaks on wrapsize-*overw-* h-*-- for equal dimensionsmin-h-dvhovermin-h-screen-- dvh accounts for mobile browser chrome- Opacity modifier (
bg-black/50) --*-opacity-*utilities are removed in v4 - Design tokens over arbitrary values -- check
@themebefore using[#hex] - Never construct classes dynamically --
text-${color}-500won't be detected; use complete class names @utilityover@applywith@layer--@applyon@layerclasses fails in v4- Parent padding over last-child margin -- use padding on containers instead of bottom margins on the last child
overflow-x-automakes a scroll container on BOTH axes -- the utility emits onlyoverflow-x: auto, but CSS Overflow 3 computes avisiblevalue on the other axis toautoonce either axis is scrollable, so a wrapper added purely to allow horizontal panning silently clips or scrolls whatever overflows it vertically: a non-portalled dropdown, popover, tooltip, custom select, or focus ring. jsdom does no layout, so no unit test catches it -- enumerate every component rendered inside the new wrapper and confirm each overlay-ish one escapes it. Portaling is opt-in, not automatic: Radix exposes it as a separate*.Portalpart wrapping*.Content, so read the component rather than assuming a library handles it. Same computed-value rule read backwards: before blaming a page-level scrollbar, walk every ancestor'soverflowand name the element that actually scrolls- Never express visibility as the native
hiddenattribute plus a display utility -- the two resolve in opposite directions across versions. v4's Preflight ships[hidden]:where(:not([hidden="until-found"])) { display: none !important }, so the attribute wins and an element expected to be visible stays hidden; on v3, or wherever Preflight is disabled or not loaded, the author-origin utility (block,flex,grid) beats the UA-origin[hidden]rule regardless of specificity and the element stays on screen withhiddenset. Toggle one mechanism:clsx(base, open ? 'block' : 'hidden'). Neither direction is visible to jsdom'stoBeInTheDocument-- onlytoBeVisibleor a real browser engine catches it
Verify
- Build passes with zero errors (
npm run buildor equivalent) - No v3 class names remain in changed files (check with
@tailwindcss/upgrade --dry-runif available) - No conflicting classes on the same element
References
- Component patterns -- tailwind-variants slots, CVA, compound components
- Layout patterns -- grid areas, container queries, z-index management, fluid typography
Task-specific references
Read the relevant reference before implementing or reviewing the matching behavior:
- For theme tokens, build configuration, version migration, dark mode, or missing styles: configuration-and-migration.md.
- For class merging, scanning paths, variants, or lint integration: class-generation-and-composition.md.
Existing specialized references, when the corresponding topic applies: