Opinionated constraints for building accessible, fast, delightful interfaces. Use MUST/SHOULD/NEVER to guide decisions.
- MUST use Tailwind CSS defaults unless custom values exist or are explicitly requested
- MUST use
cnutility (clsx+tailwind-merge) for class logic - MUST use
motion/reactwhen JavaScript animation is required - SHOULD use
tw-animate-cssfor entrance and micro-animations - SHOULD prefer Rust-based or modern tooling where available
- MUST use accessible primitives for keyboard/focus behavior (
Base UI,React Aria,Radix) - MUST use project's existing component primitives first
- SHOULD prefer
Base UIfor new primitives if stack-compatible - NEVER mix primitive systems within the same interaction surface
- NEVER rebuild keyboard or focus behavior by hand unless explicitly requested
- MUST implement full keyboard support per WAI-ARIA APG
- MUST show visible focus rings via
:focus-visible; group with:focus-within - MUST manage focus (trap, move, return) per APG patterns
- MUST make focusable elements in sequential lists navigable with ↑↓ keys
- MUST make focusable elements in sequential lists deletable with ⌘+Backspace
- SHOULD use box-shadow for focus rings (respects border-radius unlike outline)
- NEVER use
outline: nonewithout visible focus replacement - NEVER use positive
tabIndexvalues (disrupts natural tab order)
- MUST ensure hit targets ≥24px (mobile ≥44px); if visual <24px, expand hit area
- MUST set mobile
<input>font-size ≥16px to prevent iOS zoom on focus - MUST use
touch-action: manipulationto prevent double-tap zoom - MUST disable
touch-actionfor custom pan/zoom gestures to prevent native interference - SHOULD set
-webkit-tap-highlight-colorto match design; always provide alternative - SHOULD avoid autofocus on mobile (opens keyboard, covers screen)
- NEVER disable browser zoom (
user-scalable=no,maximum-scale=1)
- MUST wrap inputs with
<form>to enable Enter submission - MUST use appropriate
type(password,email, etc.) andinputmode - MUST use meaningful
nameattributes with correctautocompletevalues - MUST keep inputs hydration-safe (no lost focus/value); use
defaultValuefor uncontrolled - MUST allow incomplete form submission to surface validation errors
- MUST show errors inline next to fields; on submit, focus first error
- MUST keep submit enabled until request starts; then disable with spinner
- MUST show spinner on loading buttons while preserving original label
- MUST warn on unsaved changes before navigation
- MUST ensure compatibility with password managers and 2FA; allow pasting codes
- MUST trim values to handle trailing spaces from text expansion
- MUST ensure no dead zones on checkboxes/radios; label+control share one hit target
- MUST position prefix/suffix decorations absolutely over input with padding, triggering focus
- SHOULD disable
spellcheckfor emails, codes, usernames - SHOULD end placeholders with
…and show example pattern - SHOULD prefer uncontrolled inputs; controlled inputs must be cheap per keystroke
- NEVER block paste in
<input>or<textarea>
- MUST reflect state in URL (deep-link filters, tabs, pagination, expanded panels)
- MUST restore scroll position on Back/Forward
- MUST use
<a>/<Link>for navigation (supports Cmd/Ctrl/middle-click) - MUST perform auth redirects server-side before client loads to avoid janky URL changes
- NEVER use
<div onClick>for navigation
- MUST use
AlertDialogfor destructive or irreversible actions; or provide Undo window - MUST use polite
aria-livefor toasts and inline validation - MUST display feedback relative to trigger (inline checkmark on copy, highlight input on error)
- SHOULD use optimistic UI; reconcile on response; rollback on failure or offer Undo
- SHOULD use ellipsis (
…) for options opening follow-ups ("Rename…") and loading states ("Loading…")
- MUST use generous targets with clear affordances; avoid finicky interactions
- MUST use
overscroll-behavior: containin modals/drawers - MUST disable text selection during drag; set
inerton dragged elements - MUST delay first tooltip; subsequent peers show instantly
- MUST apply
mutedandplaysinlineto<video>for iOS autoplay - SHOULD use
@media (hover: hover)to prevent hover states flashing on touch press
- MUST honor
prefers-reduced-motion(provide reduced variant or disable) - MUST animate only compositor-friendly props (
transform,opacity) - MUST ensure animations are interruptible and input-driven (no autoplay)
- MUST set correct
transform-origin(motion starts where it physically should) - MUST pause looping animations when off-screen (offload CPU/GPU)
- MUST use SVG transforms on
<g>wrapper withtransform-box: fill-box - SHOULD prefer CSS > Web Animations API > JS libraries
- SHOULD use
ease-outon entrance - SHOULD choose easing to match the change (size, distance, trigger)
- SHOULD animate only to clarify cause/effect or add deliberate delight
- SHOULD use
scroll-behavior: smoothfor in-page anchors with appropriate offset - NEVER add animation unless explicitly requested
- NEVER animate layout props (
top,left,width,height,margin,padding) - NEVER exceed 200ms for interaction feedback
- NEVER use
transition: all—list properties explicitly - NEVER introduce custom easing curves unless explicitly requested
- NEVER animate large images, full-screen surfaces, or large
blur()/backdrop-filter
- MUST use deliberate alignment to grid/baseline/edges—no accidental placement
- MUST verify layouts on mobile, laptop, and ultra-wide (simulate at 50% zoom)
- MUST respect safe areas via
env(safe-area-inset-*)for fixed elements - MUST avoid unwanted scrollbars; fix overflows
- MUST use a fixed
z-indexscale (no arbitraryz-*values) - SHOULD use optical alignment; adjust ±1px when perception beats geometry
- SHOULD balance icon/text lockups (weight, size, spacing, color)
- SHOULD use flex/grid over JS measurement for layout
- SHOULD use
size-*for square elements instead ofw-*+h-* - NEVER use
h-screen; useh-dvh
- MUST apply
-webkit-font-smoothing: antialiasedfor legibility - MUST apply
text-rendering: optimizeLegibility - MUST use
font-variant-numeric: tabular-numsfor number comparisons, tables, timers - MUST prevent iOS landscape text resizing with
-webkit-text-size-adjust: 100% - MUST subset fonts based on content, alphabet, or relevant languages
- MUST use
text-balancefor headings;text-prettyfor body/paragraphs - SHOULD use
truncateorline-clamp-*for dense UI - SHOULD use fluid sizing via
clamp()for responsive headings - SHOULD use medium headings with font-weight 500–600
- NEVER use font weights below 400
- NEVER change font weight on hover/selected state (causes layout shift)
- NEVER modify
letter-spacing(tracking-*) unless explicitly requested
- MUST ensure
<title>matches current context - MUST add
scroll-margin-topon headings; include "Skip to content" link; use hierarchical<h1>–<h6> - MUST provide accessible names even when visuals omit labels
- MUST add
aria-labelto icon-only buttons and interactive elements - MUST use accurate
aria-label; mark decorative elements witharia-hidden - MUST use redundant status cues (not color-only); icons should have text labels
- MUST use
…character (not...) - MUST use non-breaking spaces:
10 MB,⌘ K, brand names - MUST render images with
<img>for screen readers and right-click copy - MUST design resilient layouts for user-generated content (short, average, very long)
- MUST use locale-aware formatting (
Intl.DateTimeFormat,Intl.NumberFormat) - MUST prefer native semantics (
button,a,label,table) before ARIA - MUST design empty, sparse, dense, and error states—no dead ends
- MUST match skeletons to final content layout to avoid CLS
- SHOULD use inline help first; tooltips as last resort
- SHOULD use curly quotes (" "); avoid widows/orphans with
text-wrap: balance - SHOULD give illustrations built with HTML an explicit
aria-label - SHOULD unset gradient on
::selectionfor gradient text
- MUST handle long content in text containers (
truncate,line-clamp-*,break-words) - MUST add
min-w-0to flex children to allow truncation - MUST handle empty states gracefully—no broken UI for empty strings/arrays
- MUST track and minimize re-renders (React DevTools, React Scan)
- MUST profile with CPU/network throttling
- MUST batch layout reads/writes; avoid reflows/repaints
- MUST target <500ms for mutations (
POST/PATCH/DELETE) - MUST virtualize large lists (>50 items)
- MUST preload above-fold images; lazy-load the rest
- MUST prevent CLS with explicit image dimensions
- MUST measure reliably (disable extensions that skew runtime)
- MUST pause or unmount off-screen auto-playing videos on iOS
- SHOULD test on iOS Low Power Mode and macOS Safari
- SHOULD use
<link rel="preconnect">for CDN domains - SHOULD preload critical fonts with
<link rel="preload" as="font">andfont-display: swap - SHOULD bypass React render lifecycle with refs for real-time DOM updates
- SHOULD adapt to user's hardware/network capabilities
- NEVER apply
will-changeoutside an active animation - NEVER use
useEffectfor anything expressible as render logic
- MUST set
color-scheme: darkon<html>for dark themes - MUST set explicit
background-colorandcoloron native<select>(Windows fix) - MUST prevent theme switching from triggering unintended transitions
- SHOULD set
<meta name="theme-color">to match page background - SHOULD use SVG favicon with
prefers-color-schemestyle tag
- MUST pair inputs with
valuewithonChange(or usedefaultValue) - SHOULD guard date/time rendering against hydration mismatch
- MUST style document selection with
::selection - MUST use accessible chart palettes (color-blind-friendly)
- MUST meet contrast—prefer APCA over WCAG 2
- MUST increase contrast on
:hover/:active/:focus - MUST ensure toggles take immediate effect without confirmation
- MUST disable
user-selecton inner content of interactive elements - MUST disable
pointer-eventson decorative elements (glows, gradients) - MUST ensure interactive elements in lists have no dead areas—increase
padding - MUST ensure anything that looks clickable is clickable
- MUST give empty states one clear next action with optional templates
- SHOULD use optimistic updates; roll back on server error with feedback
- SHOULD use layered shadows (ambient + direct)
- SHOULD use semi-transparent borders + shadows for crisp edges
- SHOULD ensure nested radii: child ≤ parent; concentric
- SHOULD tint borders/shadows/text toward background hue for consistency
- SHOULD match browser UI to background
- SHOULD limit accent color to one per view
- SHOULD use existing theme or Tailwind color tokens before introducing new ones
- SHOULD use nested menus with "prediction cone" to prevent accidental closing
- NEVER use gradients unless explicitly requested
- NEVER use purple or multicolor gradients
- NEVER use glow effects as primary affordances
- NEVER use dark gradient colors that cause banding (use background images instead)