|
| 1 | +Concise rules for building accessible, fast, delightful UIs Use MUST/SHOULD/NEVER to guide decisions |
| 2 | + |
| 3 | +## Interactions |
| 4 | + |
| 5 | +- Keyboard |
| 6 | + - MUST: Full keyboard support per [WAI-ARIA APG](https://wwww3org/WAI/ARIA/apg/patterns/) |
| 7 | + - MUST: Visible focus rings (`:focus-visible`; group with `:focus-within`) |
| 8 | + - MUST: Manage focus (trap, move, and return) per APG patterns |
| 9 | +- Targets & input |
| 10 | + - MUST: Hit target ≥24px (mobile ≥44px) If visual <24px, expand hit area |
| 11 | + - MUST: Mobile `<input>` font-size ≥16px or set: |
| 12 | + ```html |
| 13 | + <meta |
| 14 | + name="viewport" |
| 15 | + content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" |
| 16 | + /> |
| 17 | + ``` |
| 18 | + - NEVER: Disable browser zoom |
| 19 | + - MUST: `touch-action: manipulation` to prevent double-tap zoom; set `-webkit-tap-highlight-color` to match design |
| 20 | +- Inputs & forms (behavior) |
| 21 | + - MUST: Hydration-safe inputs (no lost focus/value) |
| 22 | + - NEVER: Block paste in `<input>/<textarea>` |
| 23 | + - MUST: Loading buttons show spinner and keep original label |
| 24 | + - MUST: Enter submits focused text input In `<textarea>`, ⌘/Ctrl+Enter submits; Enter adds newline |
| 25 | + - MUST: Keep submit enabled until request starts; then disable, show spinner, use idempotency key |
| 26 | + - MUST: Don’t block typing; accept free text and validate after |
| 27 | + - MUST: Allow submitting incomplete forms to surface validation |
| 28 | + - MUST: Errors inline next to fields; on submit, focus first error |
| 29 | + - MUST: `autocomplete` + meaningful `name`; correct `type` and `inputmode` |
| 30 | + - SHOULD: Disable spellcheck for emails/codes/usernames |
| 31 | + - SHOULD: Placeholders end with ellipsis and show example pattern (eg, `+1 (123) 456-7890`, `sk-012345…`) |
| 32 | + - MUST: Warn on unsaved changes before navigation |
| 33 | + - MUST: Compatible with password managers & 2FA; allow pasting one-time codes |
| 34 | + - MUST: Trim values to handle text expansion trailing spaces |
| 35 | + - MUST: No dead zones on checkboxes/radios; label+control share one generous hit target |
| 36 | +- State & navigation |
| 37 | + - MUST: URL reflects state (deep-link filters/tabs/pagination/expanded panels) Prefer libs like [nuqs](https://nuqs.dev) |
| 38 | + - MUST: Back/Forward restores scroll |
| 39 | + - MUST: Links are links—use `<a>/<Link>` for navigation (support Cmd/Ctrl/middle-click) |
| 40 | +- Feedback |
| 41 | + - SHOULD: Optimistic UI; reconcile on response; on failure show error and rollback or offer Undo |
| 42 | + - MUST: Confirm destructive actions or provide Undo window |
| 43 | + - MUST: Use polite `aria-live` for toasts/inline validation |
| 44 | + - SHOULD: Ellipsis (`…`) for options that open follow-ups (eg, “Rename…”) |
| 45 | +- Touch/drag/scroll |
| 46 | + - MUST: Design forgiving interactions (generous targets, clear affordances; avoid finickiness) |
| 47 | + - MUST: Delay first tooltip in a group; subsequent peers no delay |
| 48 | + - MUST: Intentional `overscroll-behavior: contain` in modals/drawers |
| 49 | + - MUST: During drag, disable text selection and set `inert` on dragged element/containers |
| 50 | + - MUST: No “dead-looking” interactive zones—if it looks clickable, it is |
| 51 | +- Autofocus |
| 52 | + - SHOULD: Autofocus on desktop when there’s a single primary input; rarely on mobile (to avoid layout shift) |
| 53 | + |
| 54 | +## Animation |
| 55 | + |
| 56 | +- MUST: Honor `prefers-reduced-motion` (provide reduced variant) |
| 57 | +- SHOULD: Prefer CSS > Web Animations API > JS libraries |
| 58 | +- MUST: Animate compositor-friendly props (`transform`, `opacity`); avoid layout/repaint props (`top/left/width/height`) |
| 59 | +- SHOULD: Animate only to clarify cause/effect or add deliberate delight |
| 60 | +- SHOULD: Choose easing to match the change (size/distance/trigger) |
| 61 | +- MUST: Animations are interruptible and input-driven (avoid autoplay) |
| 62 | +- MUST: Correct `transform-origin` (motion starts where it “physically” should) |
| 63 | + |
| 64 | +## Layout |
| 65 | + |
| 66 | +- SHOULD: Optical alignment; adjust by ±1px when perception beats geometry |
| 67 | +- MUST: Deliberate alignment to grid/baseline/edges/optical centers—no accidental placement |
| 68 | +- SHOULD: Balance icon/text lockups (stroke/weight/size/spacing/color) |
| 69 | +- MUST: Verify mobile, laptop, ultra-wide (simulate ultra-wide at 50% zoom) |
| 70 | +- MUST: Respect safe areas (use env(safe-area-inset-\*)) |
| 71 | +- MUST: Avoid unwanted scrollbars; fix overflows |
| 72 | + |
| 73 | +## Content & Accessibility |
| 74 | + |
| 75 | +- SHOULD: Inline help first; tooltips last resort |
| 76 | +- MUST: Skeletons mirror final content to avoid layout shift |
| 77 | +- MUST: `<title>` matches current context |
| 78 | +- MUST: No dead ends; always offer next step/recovery |
| 79 | +- MUST: Design empty/sparse/dense/error states |
| 80 | +- SHOULD: Curly quotes (“ ”); avoid widows/orphans |
| 81 | +- MUST: Tabular numbers for comparisons (`font-variant-numeric: tabular-nums` or a mono like Geist Mono) |
| 82 | +- MUST: Redundant status cues (not color-only); icons have text labels |
| 83 | +- MUST: Don’t ship the schema—visuals may omit labels but accessible names still exist |
| 84 | +- MUST: Use the ellipsis character `…` (not ``) |
| 85 | +- MUST: `scroll-margin-top` on headings for anchored links; include a “Skip to content” link; hierarchical `<h1–h6>` |
| 86 | +- MUST: Resilient to user-generated content (short/avg/very long) |
| 87 | +- MUST: Locale-aware dates/times/numbers/currency |
| 88 | +- MUST: Accurate names (`aria-label`), decorative elements `aria-hidden`, verify in the Accessibility Tree |
| 89 | +- MUST: Icon-only buttons have descriptive `aria-label` |
| 90 | +- MUST: Prefer native semantics (`button`, `a`, `label`, `table`) before ARIA |
| 91 | +- SHOULD: Right-clicking the nav logo surfaces brand assets |
| 92 | +- MUST: Use non-breaking spaces to glue terms: `10 MB`, `⌘ + K`, `Vercel SDK` |
| 93 | + |
| 94 | +## Performance |
| 95 | + |
| 96 | +- SHOULD: Test iOS Low Power Mode and macOS Safari |
| 97 | +- MUST: Measure reliably (disable extensions that skew runtime) |
| 98 | +- MUST: Track and minimize re-renders (React DevTools/React Scan) |
| 99 | +- MUST: Profile with CPU/network throttling |
| 100 | +- MUST: Batch layout reads/writes; avoid unnecessary reflows/repaints |
| 101 | +- MUST: Mutations (`POST/PATCH/DELETE`) target <500 ms |
| 102 | +- SHOULD: Prefer uncontrolled inputs; make controlled loops cheap (keystroke cost) |
| 103 | +- MUST: Virtualize large lists (eg, `virtua`) |
| 104 | +- MUST: Preload only above-the-fold images; lazy-load the rest |
| 105 | +- MUST: Prevent CLS from images (explicit dimensions or reserved space) |
| 106 | + |
| 107 | +## Design |
| 108 | + |
| 109 | +- SHOULD: Layered shadows (ambient + direct) |
| 110 | +- SHOULD: Crisp edges via semi-transparent borders + shadows |
| 111 | +- SHOULD: Nested radii: child ≤ parent; concentric |
| 112 | +- SHOULD: Hue consistency: tint borders/shadows/text toward bg hue |
| 113 | +- MUST: Accessible charts (color-blind-friendly palettes) |
| 114 | +- MUST: Meet contrast—prefer [APCA](https://apcacontrastcom/) over WCAG 2 |
| 115 | +- MUST: Increase contrast on `:hover/:active/:focus` |
| 116 | +- SHOULD: Match browser UI to bg |
| 117 | +- SHOULD: Avoid gradient banding (use masks when needed) |
| 118 | + |
| 119 | +## Code Style and Structure: |
| 120 | + |
| 121 | +- Write concise, technical TypeScript code with accurate examples |
| 122 | +- Use functional and declarative programming patterns; avoid classes |
| 123 | +- Prefer iteration and modularization over code duplication |
| 124 | +- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError) |
| 125 | +- Structure files: exported component, subcomponents, helpers, static content, types |
| 126 | + |
| 127 | +## Naming Conventions: |
| 128 | + |
| 129 | +- Use lowercase with dashes for directories (e.g., components/auth-wizard) |
| 130 | +- Favor named exports for components |
| 131 | + |
| 132 | +## TypeScript Usage: |
| 133 | + |
| 134 | +- Use TypeScript for all code; prefer types over interfaces UNLESS specificially for intersections (Typescript Performance) |
| 135 | +- Avoid enums; use maps instead |
| 136 | +- Use functional components with types |
| 137 | + |
| 138 | +## Syntax and Formatting: |
| 139 | + |
| 140 | +- Use the "function" keyword for pure functions |
| 141 | +- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements |
| 142 | +- Use declarative JSX |
| 143 | + |
| 144 | +## Error Handling and Validation: |
| 145 | + |
| 146 | +- Prioritize error handling: handle errors and edge cases early |
| 147 | +- Use early returns and guard clauses |
| 148 | +- Implement proper error logging and user-friendly messages |
| 149 | +- Use Zod for form validation |
| 150 | +- Model expected errors as return values in Server Actions |
| 151 | +- Use error boundaries for unexpected errors |
| 152 | + |
| 153 | +## Performance Optimization: |
| 154 | + |
| 155 | +- Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC) |
| 156 | +- Wrap client components in Suspense with fallback |
| 157 | +- Use dynamic loading for non-critical components |
| 158 | +- Optimize images: use WebP format, include size data, implement lazy loading |
| 159 | + |
| 160 | +## Key Conventions: |
| 161 | + |
| 162 | +- Use 'nuqs' for URL search parameter state management |
| 163 | +- Optimize Web Vitals (LCP, CLS, FID) |
| 164 | +- Limit 'use client': |
| 165 | + - Favor server components and Next.js SSR |
| 166 | + - Use only for Web API access in small components |
| 167 | + - Avoid for data fetching or state management |
0 commit comments