AGENTS.md
userinterface-wiki/AGENTS.mdAGENTS.md
Quality
53/100
Scores the file, not the repository.Length
10,305 words
170 headings · 286 code blocksRepository
9
— · pushed 19 days agoLast changed
3 days ago
First indexed 3 days ago.1# User Interface Wiki23**Version 3.0.0**4raphael-salaja5March 202667> **Note:**8> This document is mainly for agents and LLMs to follow when reviewing,9> generating, or refactoring UI code. Humans may also find it useful, but10> guidance here is optimized for automation and consistency by AI-assisted workflows.1112---1314## Abstract1516Comprehensive UI/UX best practices guide for web interfaces, designed for AI agents and LLMs. Contains 152 rules across 12 categories, prioritized by impact from critical (animation principles, timing functions) to incremental (morphing icons, typography). Each rule includes detailed explanations and code examples comparing incorrect vs. correct implementations.1718---1920## Table of Contents21221. [Animation Principles](#1-animation-principles) — **CRITICAL**23 - 1.1 [User Animations Under 300ms](#11-user-animations-under-300ms)24 - 1.2 [Consistent Timing for Similar Elements](#12-consistent-timing-for-similar-elements)25 - 1.3 [No Entrance Animation on Context Menus](#13-no-entrance-animation-on-context-menus)26 - 1.4 [Exponential Ramps for Natural Decay](#14-exponential-ramps-for-natural-decay)27 - 1.5 [No Linear Easing for Motion](#15-no-linear-easing-for-motion)28 - 1.6 [Active State Scale Transform](#16-active-state-scale-transform)29 - 1.7 [Subtle Squash and Stretch](#17-subtle-squash-and-stretch)30 - 1.8 [Springs for Overshoot and Settle](#18-springs-for-overshoot-and-settle)31 - 1.9 [Stagger Under 50ms Per Item](#19-stagger-under-50ms-per-item)32 - 1.10 [Single Focal Point](#110-single-focal-point)33 - 1.11 [Dim Background for Focus](#111-dim-background-for-focus)34 - 1.12 [Z-Index Layering for Animated Elements](#112-z-index-layering-for-animated-elements)352. [Timing Functions](#2-timing-functions) — **HIGH**36 - 2.1 [Springs for Gesture-Driven Motion](#21-springs-for-gesture-driven-motion)37 - 2.2 [Springs for Interruptible Motion](#22-springs-for-interruptible-motion)38 - 2.3 [Springs Preserve Input Velocity](#23-springs-preserve-input-velocity)39 - 2.4 [Balanced Spring Parameters](#24-balanced-spring-parameters)40 - 2.5 [Easing for System State Changes](#25-easing-for-system-state-changes)41 - 2.6 [Ease-Out for Entrances](#26-ease-out-for-entrances)42 - 2.7 [Ease-In for Exits](#27-ease-in-for-exits)43 - 2.8 [Ease-In-Out for View Transitions](#28-ease-in-out-for-view-transitions)44 - 2.9 [Linear Easing Only for Progress](#29-linear-easing-only-for-progress)45 - 2.10 [Press and Hover 120-180ms](#210-press-and-hover-120-180ms)46 - 2.11 [Small State Changes 180-260ms](#211-small-state-changes-180-260ms)47 - 2.12 [Max 300ms for User Actions](#212-max-300ms-for-user-actions)48 - 2.13 [Shorten Duration Before Adjusting Curve](#213-shorten-duration-before-adjusting-curve)49 - 2.14 [No Animation for High-Frequency Interactions](#214-no-animation-for-high-frequency-interactions)50 - 2.15 [No Animation for Keyboard Navigation](#215-no-animation-for-keyboard-navigation)51 - 2.16 [No Entrance Animation for Context Menus](#216-no-entrance-animation-for-context-menus)523. [Exit Animations](#3-exit-animations) — **HIGH**53 - 3.1 [AnimatePresence Wrapper Required](#31-animatepresence-wrapper-required)54 - 3.2 [Exit Prop Required Inside AnimatePresence](#32-exit-prop-required-inside-animatepresence)55 - 3.3 [Unique Keys in AnimatePresence Lists](#33-unique-keys-in-animatepresence-lists)56 - 3.4 [Exit Mirrors Initial for Symmetry](#34-exit-mirrors-initial-for-symmetry)57 - 3.5 [useIsPresent in Child Component](#35-useispresent-in-child-component)58 - 3.6 [Call safeToRemove After Async Work](#36-call-safetoremove-after-async-work)59 - 3.7 [Disable Interactions on Exiting Elements](#37-disable-interactions-on-exiting-elements)60 - 3.8 [Mode "wait" Doubles Duration](#38-mode-wait-doubles-duration)61 - 3.9 [Mode "sync" Causes Layout Conflicts](#39-mode-sync-causes-layout-conflicts)62 - 3.10 [popLayout for List Reordering](#310-poplayout-for-list-reordering)63 - 3.11 [Propagate Prop for Nested AnimatePresence](#311-propagate-prop-for-nested-animatepresence)64 - 3.12 [Coordinated Parent-Child Exit Timing](#312-coordinated-parent-child-exit-timing)654. [CSS Pseudo Elements](#4-css-pseudo-elements) — **MEDIUM**66 - 4.1 [Content Property Required for Pseudo-Elements](#41-content-property-required-for-pseudo-elements)67 - 4.2 [Pseudo-Elements Over DOM Nodes](#42-pseudo-elements-over-dom-nodes)68 - 4.3 [Position Relative Parent for Pseudo-Elements](#43-position-relative-parent-for-pseudo-elements)69 - 4.4 [Z-Index Layering for Pseudo-Elements](#44-z-index-layering-for-pseudo-elements)70 - 4.5 [Hit Target Expansion with Pseudo-Elements](#45-hit-target-expansion-with-pseudo-elements)71 - 4.6 [View Transition Name Required](#46-view-transition-name-required)72 - 4.7 [Unique View Transition Names](#47-unique-view-transition-names)73 - 4.8 [Clean Up View Transition Names](#48-clean-up-view-transition-names)74 - 4.9 [View Transitions Over JS Libraries](#49-view-transitions-over-js-libraries)75 - 4.10 [Style View Transition Pseudo-Elements](#410-style-view-transition-pseudo-elements)76 - 4.11 [Use ::backdrop for Dialog Backgrounds](#411-use-backdrop-for-dialog-backgrounds)77 - 4.12 [Use ::placeholder for Input Styling](#412-use-placeholder-for-input-styling)78 - 4.13 [Use ::selection for Text Styling](#413-use-selection-for-text-styling)79 - 4.14 [Use ::marker for Custom List Bullets](#414-use-marker-for-custom-list-bullets)80 - 4.15 [Use ::first-line for Typographic Treatments](#415-use-first-line-for-typographic-treatments)815. [Audio Feedback](#5-audio-feedback) — **MEDIUM**82 - 5.1 [Visual Equivalent for Every Sound](#51-visual-equivalent-for-every-sound)83 - 5.2 [Toggle Setting to Disable Sounds](#52-toggle-setting-to-disable-sounds)84 - 5.3 [Respect prefers-reduced-motion for Sound](#53-respect-prefers-reduced-motion-for-sound)85 - 5.4 [Independent Volume Control](#54-independent-volume-control)86 - 5.5 [No Sound on High-Frequency Interactions](#55-no-sound-on-high-frequency-interactions)87 - 5.6 [Sound for Confirmations](#56-sound-for-confirmations)88 - 5.7 [Sound for Errors and Warnings](#57-sound-for-errors-and-warnings)89 - 5.8 [No Decorative Sound](#58-no-decorative-sound)90 - 5.9 [Informative Not Punishing Sound](#59-informative-not-punishing-sound)91 - 5.10 [Preload Audio Files](#510-preload-audio-files)92 - 5.11 [Subtle Default Volume](#511-subtle-default-volume)93 - 5.12 [Reset currentTime Before Replay](#512-reset-currenttime-before-replay)94 - 5.13 [Match Sound Weight to Action](#513-match-sound-weight-to-action)95 - 5.14 [Sound Duration Matches Action Duration](#514-sound-duration-matches-action-duration)966. [Sound Synthesis](#6-sound-synthesis) — **MEDIUM**97 - 6.1 [Reuse Single AudioContext](#61-reuse-single-audiocontext)98 - 6.2 [Resume Suspended AudioContext](#62-resume-suspended-audiocontext)99 - 6.3 [Clean Up Audio Nodes After Playback](#63-clean-up-audio-nodes-after-playback)100 - 6.4 [Exponential Decay for Natural Sound](#64-exponential-decay-for-natural-sound)101 - 6.5 [No Zero Target for Exponential Ramps](#65-no-zero-target-for-exponential-ramps)102 - 6.6 [Set Initial Value Before Ramp](#66-set-initial-value-before-ramp)103 - 6.7 [Noise for Percussive Sounds](#67-noise-for-percussive-sounds)104 - 6.8 [Oscillators for Tonal Sounds](#68-oscillators-for-tonal-sounds)105 - 6.9 [Bandpass Filter for Sound Character](#69-bandpass-filter-for-sound-character)106 - 6.10 [Click Duration 5-15ms](#610-click-duration-5-15ms)107 - 6.11 [Click Filter 3000-6000Hz](#611-click-filter-3000-6000hz)108 - 6.12 [Gain Under 1.0](#612-gain-under-10)109 - 6.13 [Filter Q Value 2-5](#613-filter-q-value-2-5)1107. [Morphing Icons](#7-morphing-icons) — **LOW**111 - 7.1 [Icons Must Use Exactly Three Lines](#71-icons-must-use-exactly-three-lines)112 - 7.2 [Use Collapsed Constant for Unused Lines](#72-use-collapsed-constant-for-unused-lines)113 - 7.3 [Consistent ViewBox Size](#73-consistent-viewbox-size)114 - 7.4 [Shared Group for Rotational Variants](#74-shared-group-for-rotational-variants)115 - 7.5 [Spring Physics for Rotation](#75-spring-physics-for-rotation)116 - 7.6 [Reduced Motion Support for Icons](#76-reduced-motion-support-for-icons)117 - 7.7 [Instant Jump for Non-Grouped Icons](#77-instant-jump-for-non-grouped-icons)118 - 7.8 [Round Stroke Line Caps](#78-round-stroke-line-caps)119 - 7.9 [Aria Hidden on Icon SVGs](#79-aria-hidden-on-icon-svgs)1208. [Container Animation](#8-container-animation) — **MEDIUM**121 - 8.1 [Two-Div Pattern for Animated Bounds](#81-two-div-pattern-for-animated-bounds)122 - 8.2 [Guard Against Zero on Initial Render](#82-guard-against-zero-on-initial-render)123 - 8.3 [Use ResizeObserver for Measurement](#83-use-resizeobserver-for-measurement)124 - 8.4 [Overflow Hidden on Animated Container](#84-overflow-hidden-on-animated-container)125 - 8.5 [Use Animated Bounds Sparingly](#85-use-animated-bounds-sparingly)126 - 8.6 [Use Callback Ref for Measurement](#86-use-callback-ref-for-measurement)127 - 8.7 [Add Delay for Natural Container Transitions](#87-add-delay-for-natural-container-transitions)1289. [Laws of UX](#9-laws-of-ux) — **HIGH**129 - 9.1 [Size Interactive Targets for Easy Clicking](#91-size-interactive-targets-for-easy-clicking)130 - 9.2 [Expand Hit Areas with Invisible Padding](#92-expand-hit-areas-with-invisible-padding)131 - 9.3 [Minimize Choices to Reduce Decision Time](#93-minimize-choices-to-reduce-decision-time)132 - 9.4 [Chunk Data into Groups of 5-9](#94-chunk-data-into-groups-of-5-9)133 - 9.5 [Respond Within 400ms](#95-respond-within-400ms)134 - 9.6 [Fake Speed When Actual Speed Isn't Possible](#96-fake-speed-when-actual-speed-isnt-possible)135 - 9.7 [Accept Messy Input, Output Clean Data](#97-accept-messy-input-output-clean-data)136 - 9.8 [Show What Matters Now, Reveal Complexity Later](#98-show-what-matters-now-reveal-complexity-later)137 - 9.9 [Use Familiar UI Patterns](#99-use-familiar-ui-patterns)138 - 9.10 [Visual Polish Increases Perceived Usability](#910-visual-polish-increases-perceived-usability)139 - 9.11 [Group Related Elements Spatially](#911-group-related-elements-spatially)140 - 9.12 [Similar Elements Should Look Alike](#912-similar-elements-should-look-alike)141 - 9.13 [Use Boundaries to Group Related Content](#913-use-boundaries-to-group-related-content)142 - 9.14 [Make Important Elements Visually Distinct](#914-make-important-elements-visually-distinct)143 - 9.15 [Place Key Items First or Last](#915-place-key-items-first-or-last)144 - 9.16 [End Experiences with Clear Success States](#916-end-experiences-with-clear-success-states)145 - 9.17 [Move Complexity to the System](#917-move-complexity-to-the-system)146 - 9.18 [Show Progress Toward Completion](#918-show-progress-toward-completion)147 - 9.19 [Show Incomplete State to Drive Completion](#919-show-incomplete-state-to-drive-completion)148 - 9.20 [Simplify Complex Visuals into Clear Forms](#920-simplify-complex-visuals-into-clear-forms)149 - 9.21 [Prioritize the Critical 20% of Features](#921-prioritize-the-critical-20-of-features)150 - 9.22 [Minimize Extraneous Cognitive Load](#922-minimize-extraneous-cognitive-load)151 - 9.23 [Visually Connect Related Elements](#923-visually-connect-related-elements)15210. [Predictive Prefetching](#10-predictive-prefetching) — **MEDIUM**153 - 10.1 [Trajectory Prediction Over Hover Prefetching](#101-trajectory-prediction-over-hover-prefetching)154 - 10.2 [Prefetch by Intent, Not Viewport](#102-prefetch-by-intent-not-viewport)155 - 10.3 [Use hitSlop to Trigger Predictions Earlier](#103-use-hitslop-to-trigger-predictions-earlier)156 - 10.4 [Fall Back Gracefully on Touch Devices](#104-fall-back-gracefully-on-touch-devices)157 - 10.5 [Prefetch on Keyboard Navigation](#105-prefetch-on-keyboard-navigation)158 - 10.6 [Use Predictive Prefetching Selectively](#106-use-predictive-prefetching-selectively)15911. [Typography](#11-typography) — **MEDIUM**160 - 11.1 [Tabular Numbers for Data Display](#111-tabular-numbers-for-data-display)161 - 11.2 [Oldstyle Numbers for Body Text](#112-oldstyle-numbers-for-body-text)162 - 11.3 [Slashed Zero for Disambiguation](#113-slashed-zero-for-disambiguation)163 - 11.4 [Enable Contextual Alternates](#114-enable-contextual-alternates)164 - 11.5 [Use Disambiguation Stylistic Set for UI](#115-use-disambiguation-stylistic-set-for-ui)165 - 11.6 [Keep Optical Sizing Auto](#116-keep-optical-sizing-auto)166 - 11.7 [Use Antialiased Font Smoothing](#117-use-antialiased-font-smoothing)167 - 11.8 [Balance Headings with text-wrap](#118-balance-headings-with-text-wrap)168 - 11.9 [Offset Underlines from Descenders](#119-offset-underlines-from-descenders)169 - 11.10 [Disable Font Synthesis for Missing Styles](#1110-disable-font-synthesis-for-missing-styles)170 - 11.11 [Use font-display swap](#1111-use-font-display-swap)171 - 11.12 [Continuous Weight Values with Variable Fonts](#1112-continuous-weight-values-with-variable-fonts)172 - 11.13 [text-wrap pretty for Body Text](#1113-text-wrap-pretty-for-body-text)173 - 11.14 [Pair Justified Text with Hyphens](#1114-pair-justified-text-with-hyphens)174 - 11.15 [Add Letter Spacing to Uppercase Text](#1115-add-letter-spacing-to-uppercase-text)175 - 11.16 [Use Typographic Fractions](#1116-use-typographic-fractions)17612. [Visual Design](#12-visual-design) — **HIGH**177 - 12.1 [Concentric Border Radius for Nested Elements](#121-concentric-border-radius-for-nested-elements)178 - 12.2 [Layer Multiple Shadows for Realistic Depth](#122-layer-multiple-shadows-for-realistic-depth)179 - 12.3 [Consistent Shadow Direction Across UI](#123-consistent-shadow-direction-across-ui)180 - 12.4 [Use Neutral Colors for Shadows](#124-use-neutral-colors-for-shadows)181 - 12.5 [Shadow Size Indicates Elevation](#125-shadow-size-indicates-elevation)182 - 12.6 [Animate Shadows via Pseudo-Element Opacity](#126-animate-shadows-via-pseudo-element-opacity)183 - 12.7 [Use a Consistent Spacing Scale](#127-use-a-consistent-spacing-scale)184 - 12.8 [Use Semi-Transparent Borders](#128-use-semi-transparent-borders)185 - 12.9 [Full Shadow Anatomy on Buttons](#129-full-shadow-anatomy-on-buttons)186187---188189## 1. Animation Principles190191**Impact:** CRITICAL — Disney's 12 principles adapted for web. Violations here produce the most noticeable quality issues.192193### 1.1 User Animations Under 300ms194195User-initiated animations must complete within 300ms.196197**Incorrect (exceeds 300ms limit):**198199```css200.button { transition: transform 400ms; }201```202203**Correct (within 300ms):**204205```css206.button { transition: transform 200ms; }207```208209### 1.2 Consistent Timing for Similar Elements210211Similar elements must use identical timing values.212213**Incorrect (inconsistent timing):**214215```css216.button-primary { transition: 200ms; }217.button-secondary { transition: 150ms; }218```219220**Correct (consistent timing):**221222```css223.button-primary { transition: 200ms; }224.button-secondary { transition: 200ms; }225```226227### 1.3 No Entrance Animation on Context Menus228229Context menus should not animate on entrance (exit only).230231**Incorrect (animates entrance):**232233```tsx234<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />235```236237**Correct (exit only):**238239```tsx240<motion.div exit={{ opacity: 0 }} />241```242243### 1.4 Exponential Ramps for Natural Decay244245Use exponential ramps, not linear, for natural decay.246247**Incorrect (linear ramp):**248249```ts250gain.gain.linearRampToValueAtTime(0, t + 0.05);251```252253**Correct (exponential ramp):**254255```ts256gain.gain.exponentialRampToValueAtTime(0.001, t + 0.05);257```258259### 1.5 No Linear Easing for Motion260261Linear easing should only be used for progress indicators, not motion.262263**Incorrect (linear for motion):**264265```css266.card { transition: transform 200ms linear; }267```268269**Correct (linear for progress only):**270271```css272.progress-bar { transition: width 100ms linear; }273```274275### 1.6 Active State Scale Transform276277Interactive elements must have active/pressed state with scale transform.278279**Incorrect (no active state):**280281```css282.button:hover { background: var(--gray-3); }283/* Missing :active state */284```285286**Correct (active state present):**287288```css289.button:active { transform: scale(0.98); }290```291292### 1.7 Subtle Squash and Stretch293294Squash/stretch deformation must be subtle (0.95-1.05 range).295296**Incorrect (excessive deformation):**297298```tsx299<motion.div whileTap={{ scale: 0.8 }} />300```301302**Correct (subtle deformation):**303304```tsx305<motion.div whileTap={{ scale: 0.98 }} />306```307308### 1.8 Springs for Overshoot and Settle309310Use springs (not easing) when overshoot-and-settle is needed.311312**Incorrect (easing for bounce):**313314```tsx315<motion.div transition={{ duration: 0.3, ease: "easeOut" }} />316```317318**Correct (spring physics):**319320```tsx321<motion.div transition={{ type: "spring", stiffness: 500, damping: 30 }} />322```323324### 1.9 Stagger Under 50ms Per Item325326Stagger delays must not exceed 50ms per item.327328**Incorrect (excessive stagger):**329330```tsx331transition={{ staggerChildren: 0.15 }}332```333334**Correct (reasonable stagger):**335336```tsx337transition={{ staggerChildren: 0.03 }}338```339340### 1.10 Single Focal Point341342Only one element should animate prominently at a time.343344**Incorrect (competing animations):**345346```tsx347<motion.div animate={{ scale: 1.1 }} />348<motion.div animate={{ scale: 1.1 }} />349```350351### 1.11 Dim Background for Focus352353Modal/dialog backgrounds should dim to direct focus.354355**Incorrect (transparent overlay):**356357```css358.overlay { background: transparent; }359```360361**Correct (dimmed overlay):**362363```css364.overlay { background: var(--black-a6); }365```366367### 1.12 Z-Index Layering for Animated Elements368369Animated elements must respect z-index layering.370371**Incorrect (no z-index):**372373```css374.tooltip { /* No z-index, may render behind other elements */ }375```376377**Correct (explicit z-index):**378379```css380.tooltip { z-index: 50; }381```382383---384385## 2. Timing Functions386387**Impact:** HIGH — Choosing the right timing function based on whether motion is user-driven, system-driven, or high-frequency.388389**Decision framework:** Is this motion reacting to the user, or is the system speaking?390391| Motion Type | Best Choice | Why |392|-------------|-------------|-----|393| User-driven (drag, flick, gesture) | Spring | Survives interruption, preserves velocity |394| System-driven (state change, feedback) | Easing | Clear start/end, predictable timing |395| Time representation (progress, loading) | Linear | 1:1 relationship between time and progress |396| High-frequency (typing, fast toggles) | None | Animation adds noise, feels slower |397398### 2.1 Springs for Gesture-Driven Motion399400Gesture-driven motion (drag, flick, swipe) must use springs.401402**Incorrect (easing for drag):**403404```tsx405<motion.div406 drag="x"407 transition={{ duration: 0.3, ease: "easeOut" }}408/>409```410411**Correct (spring for drag):**412413```tsx414<motion.div415 drag="x"416 transition={{ type: "spring", stiffness: 500, damping: 30 }}417/>418```419420### 2.2 Springs for Interruptible Motion421422Motion that can be interrupted must use springs.423424**Incorrect (easing for interruptible):**425426```tsx427<motion.div428 animate={{ x: isOpen ? 200 : 0 }}429 transition={{ duration: 0.3 }}430/>431```432433**Correct (spring for interruptible):**434435```tsx436<motion.div437 animate={{ x: isOpen ? 200 : 0 }}438 transition={{ type: "spring", stiffness: 400, damping: 25 }}439/>440```441442### 2.3 Springs Preserve Input Velocity443444When velocity matters, use springs to preserve input energy.445446**Incorrect (velocity ignored):**447448```tsx449onDragEnd={(e, info) => {450 animate(target, { x: 0 }, { duration: 0.3 });451}}452```453454**Correct (velocity preserved):**455456```tsx457onDragEnd={(e, info) => {458 animate(target, { x: 0 }, {459 type: "spring",460 velocity: info.velocity.x,461 });462}}463```464465### 2.4 Balanced Spring Parameters466467Spring parameters must be balanced; avoid excessive oscillation.468469**Incorrect (too bouncy):**470471```tsx472transition={{473 type: "spring",474 stiffness: 1000,475 damping: 5,476}}477```478479**Correct (balanced):**480481```tsx482transition={{483 type: "spring",484 stiffness: 500,485 damping: 30,486}}487```488489### 2.5 Easing for System State Changes490491System-initiated state changes should use easing curves.492493**Incorrect (spring for announcement):**494495```tsx496<motion.div497 animate={{ y: 0 }}498 transition={{ type: "spring" }}499/>500```501502**Correct (easing for announcement):**503504```tsx505<motion.div506 animate={{ y: 0 }}507 transition={{ duration: 0.2, ease: "easeOut" }}508/>509```510511### 2.6 Ease-Out for Entrances512513Entrances must use ease-out (arrive fast, settle gently).514515**Incorrect (ease-in for entrance):**516517```css518.modal-enter { animation-timing-function: ease-in; }519```520521**Correct (ease-out for entrance):**522523```css524.modal-enter { animation-timing-function: ease-out; }525```526527### 2.7 Ease-In for Exits528529Exits must use ease-in (build momentum before departure).530531**Incorrect (ease-out for exit):**532533```css534.modal-exit { animation-timing-function: ease-out; }535```536537**Correct (ease-in for exit):**538539```css540.modal-exit { animation-timing-function: ease-in; }541```542543### 2.8 Ease-In-Out for View Transitions544545View/mode transitions use ease-in-out for neutral attention.546547**Correct:**548549```css550.page-transition { animation-timing-function: ease-in-out; }551```552553### 2.9 Linear Easing Only for Progress554555Linear easing only for progress bars and time representation.556557**Incorrect (linear for motion):**558559```css560.card-slide { transition: transform 200ms linear; }561```562563**Correct (linear for progress):**564565```css566.progress-bar { transition: width 100ms linear; }567```568569### 2.10 Press and Hover 120-180ms570571Press and hover interactions should use 120-180ms duration.572573**Incorrect (too slow):**574575```css576.button:hover { transition: background-color 400ms; }577```578579**Correct (appropriate duration):**580581```css582.button:hover { transition: background-color 150ms; }583```584585### 2.11 Small State Changes 180-260ms586587Small state changes should use 180-260ms duration.588589**Correct:**590591```css592.toggle { transition: transform 200ms ease; }593```594595### 2.12 Max 300ms for User Actions596597User-initiated animations must not exceed 300ms.598599**Incorrect (exceeds limit):**600601```tsx602<motion.div transition={{ duration: 0.5 }} />603```604605**Correct (within limit):**606607```tsx608<motion.div transition={{ duration: 0.25 }} />609```610611### 2.13 Shorten Duration Before Adjusting Curve612613If animation feels slow, shorten duration before adjusting curve.614615**Incorrect (adjusting curve instead):**616617```css618.element { transition: 400ms cubic-bezier(0, 0.9, 0.1, 1); }619```620621**Correct (shorter duration):**622623```css624.element { transition: 200ms ease-out; }625```626627### 2.14 No Animation for High-Frequency Interactions628629High-frequency interactions should have no animation.630631**Incorrect (animated on every keystroke):**632633```tsx634function SearchInput() {635 return (636 <motion.div animate={{ scale: [1, 1.02, 1] }}>637 <input onChange={handleSearch} />638 </motion.div>639 );640}641```642643**Correct (no animation):**644645```tsx646function SearchInput() {647 return <input onChange={handleSearch} />;648}649```650651### 2.15 No Animation for Keyboard Navigation652653Keyboard navigation should be instant, no animation.654655**Incorrect (animated focus):**656657```tsx658function Menu() {659 return items.map(item => (660 <motion.li661 whileFocus={{ scale: 1.05 }}662 transition={{ duration: 0.2 }}663 />664 ));665}666```667668**Correct (CSS focus-visible only):**669670```tsx671function Menu() {672 return items.map(item => (673 <li className={styles.menuItem} />674 ));675}676```677678### 2.16 No Entrance Animation for Context Menus679680Context menus should not animate on entrance (exit only).681682**Incorrect (entrance animation):**683684```tsx685<motion.div686 initial={{ opacity: 0, scale: 0.95 }}687 animate={{ opacity: 1, scale: 1 }}688 exit={{ opacity: 0 }}689/>690```691692**Correct (exit only):**693694```tsx695<motion.div exit={{ opacity: 0, scale: 0.95 }} />696```697698**Quick reference:**699700| Interaction | Timing | Type |701|-------------|--------|------|702| Drag release | Spring | `stiffness: 500, damping: 30` |703| Button press | 150ms | `ease` |704| Modal enter | 200ms | `ease-out` |705| Modal exit | 150ms | `ease-in` |706| Page transition | 250ms | `ease-in-out` |707| Progress bar | varies | `linear` |708| Typing feedback | 0ms | none |709710---711712## 3. Exit Animations713714**Impact:** HIGH — Correct AnimatePresence usage prevents layout shifts, stale interactions, and orphaned elements.715716### 3.1 AnimatePresence Wrapper Required717718Conditional motion elements must be wrapped in AnimatePresence.719720**Incorrect (no wrapper):**721722```tsx723{isVisible && (724 <motion.div exit={{ opacity: 0 }} />725)}726```727728**Correct (wrapped):**729730```tsx731<AnimatePresence>732 {isVisible && (733 <motion.div exit={{ opacity: 0 }} />734 )}735</AnimatePresence>736```737738### 3.2 Exit Prop Required Inside AnimatePresence739740Elements inside AnimatePresence should have exit prop defined.741742**Incorrect (missing exit):**743744```tsx745<AnimatePresence>746 {isOpen && (747 <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />748 )}749</AnimatePresence>750```751752**Correct (exit defined):**753754```tsx755<AnimatePresence>756 {isOpen && (757 <motion.div758 initial={{ opacity: 0 }}759 animate={{ opacity: 1 }}760 exit={{ opacity: 0 }}761 />762 )}763</AnimatePresence>764```765766### 3.3 Unique Keys in AnimatePresence Lists767768Dynamic lists inside AnimatePresence must have unique keys.769770**Incorrect (index as key):**771772```tsx773<AnimatePresence>774 {items.map((item, index) => (775 <motion.div key={index} exit={{ opacity: 0 }} />776 ))}777</AnimatePresence>778```779780**Correct (stable unique key):**781782```tsx783<AnimatePresence>784 {items.map((item) => (785 <motion.div key={item.id} exit={{ opacity: 0 }} />786 ))}787</AnimatePresence>788```789790### 3.4 Exit Mirrors Initial for Symmetry791792Exit animation should mirror initial for symmetry.793794**Incorrect (asymmetric exit):**795796```tsx797<motion.div798 initial={{ opacity: 0, y: 20 }}799 animate={{ opacity: 1, y: 0 }}800 exit={{ scale: 0 }}801/>802```803804**Correct (symmetric exit):**805806```tsx807<motion.div808 initial={{ opacity: 0, y: 20 }}809 animate={{ opacity: 1, y: 0 }}810 exit={{ opacity: 0, y: 20 }}811/>812```813814### 3.5 useIsPresent in Child Component815816useIsPresent must be called from child of AnimatePresence, not parent.817818**Incorrect (hook in parent):**819820```tsx821function Parent() {822 const isPresent = useIsPresent();823 return (824 <AnimatePresence>825 {show && <Child />}826 </AnimatePresence>827 );828}829```830831**Correct (hook in child):**832833```tsx834function Child() {835 const isPresent = useIsPresent();836 return <motion.div data-exiting={!isPresent} />;837}838```839840### 3.6 Call safeToRemove After Async Work841842When using usePresence, always call safeToRemove after async work.843844**Incorrect (missing safeToRemove):**845846```tsx847function AsyncComponent() {848 const [isPresent, safeToRemove] = usePresence();849850 useEffect(() => {851 if (!isPresent) {852 cleanup();853 }854 }, [isPresent]);855}856```857858**Correct (safeToRemove called):**859860```tsx861function AsyncComponent() {862 const [isPresent, safeToRemove] = usePresence();863864 useEffect(() => {865 if (!isPresent) {866 cleanup().then(safeToRemove);867 }868 }, [isPresent, safeToRemove]);869}870```871872### 3.7 Disable Interactions on Exiting Elements873874Disable interactions on exiting elements using isPresent.875876**Incorrect (clickable during exit):**877878```tsx879function Card() {880 const isPresent = useIsPresent();881 return <button onClick={handleClick}>Click</button>;882}883```884885**Correct (disabled during exit):**886887```tsx888function Card() {889 const isPresent = useIsPresent();890 return (891 <button onClick={handleClick} disabled={!isPresent}>892 Click893 </button>894 );895}896```897898### 3.8 Mode "wait" Doubles Duration899900Mode "wait" nearly doubles animation duration; adjust timing accordingly.901902**Incorrect (too slow with wait):**903904```tsx905<AnimatePresence mode="wait">906 <motion.div transition={{ duration: 0.3 }} />907</AnimatePresence>908```909910**Correct (halved timing):**911912```tsx913<AnimatePresence mode="wait">914 <motion.div transition={{ duration: 0.15 }} />915</AnimatePresence>916```917918### 3.9 Mode "sync" Causes Layout Conflicts919920Mode "sync" causes layout conflicts; position exiting elements absolutely.921922**Incorrect (sync with layout competition):**923924```tsx925<AnimatePresence mode="sync">926 {items.map(item => (927 <motion.div exit={{ opacity: 0 }}>{item}</motion.div>928 ))}929</AnimatePresence>930```931932**Correct (popLayout instead):**933934```tsx935<AnimatePresence mode="popLayout">936 {items.map(item => (937 <motion.div exit={{ opacity: 0 }}>{item}</motion.div>938 ))}939</AnimatePresence>940```941942### 3.10 popLayout for List Reordering943944Use popLayout mode for list reordering animations.945946**Incorrect (default mode causes shifts):**947948```tsx949<AnimatePresence>950 {items.map(item => <ListItem key={item.id} />)}951</AnimatePresence>952```953954**Correct (popLayout prevents shifts):**955956```tsx957<AnimatePresence mode="popLayout">958 {items.map(item => <ListItem key={item.id} />)}959</AnimatePresence>960```961962### 3.11 Propagate Prop for Nested AnimatePresence963964Nested AnimatePresence must use propagate prop for coordinated exits.965966**Incorrect (children vanish instantly):**967968```tsx969<AnimatePresence>970 {isOpen && (971 <motion.div exit={{ opacity: 0 }}>972 <AnimatePresence>973 {items.map(item => (974 <motion.div key={item.id} exit={{ scale: 0 }} />975 ))}976 </AnimatePresence>977 </motion.div>978 )}979</AnimatePresence>980```981982**Correct (propagate on both):**983984```tsx985<AnimatePresence propagate>986 {isOpen && (987 <motion.div exit={{ opacity: 0 }}>988 <AnimatePresence propagate>989 {items.map(item => (990 <motion.div key={item.id} exit={{ scale: 0 }} />991 ))}992 </AnimatePresence>993 </motion.div>994 )}995</AnimatePresence>996```997998### 3.12 Coordinated Parent-Child Exit Timing9991000Parent and child exit durations should be coordinated.10011002**Incorrect (parent too fast):**10031004```tsx1005<motion.div exit={{ opacity: 0 }} transition={{ duration: 0.1 }}>1006 <motion.div exit={{ scale: 0 }} transition={{ duration: 0.5 }} />1007</motion.div>1008```10091010**Correct (coordinated timing):**10111012```tsx1013<motion.div exit={{ opacity: 0 }} transition={{ duration: 0.2 }}>1014 <motion.div exit={{ scale: 0 }} transition={{ duration: 0.15 }} />1015</motion.div>1016```10171018Reference: [Motion AnimatePresence Documentation](https://motion.dev/docs/react-animate-presence)10191020---10211022## 4. CSS Pseudo Elements10231024**Impact:** MEDIUM — Leveraging pseudo-elements and View Transitions to reduce DOM nodes and improve transitions.10251026### 4.1 Content Property Required for Pseudo-Elements10271028::before and ::after require content property to render.10291030**Incorrect (missing content):**10311032```css1033.button::before {1034 position: absolute;1035 background: var(--gray-3);1036}1037```10381039**Correct (content set):**10401041```css1042.button::before {1043 content: "";1044 position: absolute;1045 background: var(--gray-3);1046}1047```10481049### 4.2 Pseudo-Elements Over DOM Nodes10501051Use pseudo-elements for decorative content instead of extra DOM nodes.10521053**Incorrect (extra DOM node):**10541055```tsx1056<button className={styles.button}>1057 <span className={styles.background} />1058 Click me1059</button>1060```10611062**Correct (pseudo-element):**10631064```tsx1065<button className={styles.button}>1066 Click me1067</button>1068```10691070```css1071.button::before {1072 content: "";1073 /* decorative background */1074}1075```10761077### 4.3 Position Relative Parent for Pseudo-Elements10781079Parent must have position: relative for absolute pseudo-elements.10801081**Incorrect (no position on parent):**10821083```css1084.button::before {1085 content: "";1086 position: absolute;1087 inset: 0;1088}1089/* .button has no position */1090```10911092**Correct (parent positioned):**10931094```css1095.button {1096 position: relative;1097}10981099.button::before {1100 content: "";1101 position: absolute;1102 inset: 0;1103}1104```11051106### 4.4 Z-Index Layering for Pseudo-Elements11071108Pseudo-elements need z-index to layer correctly with content.11091110**Incorrect (covers button text):**11111112```css1113.button::before {1114 content: "";1115 position: absolute;1116 inset: 0;1117 background: var(--gray-3);1118}1119```11201121**Correct (layered behind):**11221123```css1124.button {1125 position: relative;1126 z-index: 1;1127}11281129.button::before {1130 content: "";1131 position: absolute;1132 inset: 0;1133 background: var(--gray-3);1134 z-index: -1;1135}1136```11371138### 4.5 Hit Target Expansion with Pseudo-Elements11391140Use negative inset values to expand hit targets without extra markup.11411142**Incorrect (wrapper for hit target):**11431144```tsx1145<div className={styles.wrapper}>1146 <a className={styles.link}>Link</a>1147</div>1148```11491150**Correct (pseudo-element expansion):**11511152```css1153.link {1154 position: relative;1155}11561157.link::before {1158 content: "";1159 position: absolute;1160 inset: -8px -12px;1161}1162```11631164### 4.6 View Transition Name Required11651166Elements participating in view transitions need view-transition-name.11671168**Incorrect (no transition name):**11691170```ts1171document.startViewTransition(() => {1172 targetImg.src = newSrc;1173});1174```11751176**Correct (transition name assigned):**11771178```ts1179sourceImg.style.viewTransitionName = "card";1180document.startViewTransition(() => {1181 sourceImg.style.viewTransitionName = "";1182 targetImg.style.viewTransitionName = "card";1183});1184```11851186### 4.7 Unique View Transition Names11871188Each view-transition-name must be unique on the page during transition.11891190**Incorrect (duplicate names):**11911192```css1193.card {1194 view-transition-name: card;1195}1196/* Multiple cards with same name */1197```11981199**Correct (unique per element):**12001201```ts1202element.style.viewTransitionName = `card-${id}`;1203```12041205### 4.8 Clean Up View Transition Names12061207Remove view-transition-name after transition completes.12081209**Incorrect (stale name):**12101211```ts1212sourceImg.style.viewTransitionName = "card";1213document.startViewTransition(() => {1214 targetImg.style.viewTransitionName = "card";1215});1216```12171218**Correct (name cleaned up):**12191220```ts1221sourceImg.style.viewTransitionName = "card";1222document.startViewTransition(() => {1223 sourceImg.style.viewTransitionName = "";1224 targetImg.style.viewTransitionName = "card";1225});1226```12271228### 4.9 View Transitions Over JS Libraries12291230Prefer View Transitions API over JavaScript animation libraries for page transitions.12311232**Incorrect (JS-based transition):**12331234```tsx1235import { motion } from "motion/react";12361237function ImageLightbox() {1238 return (1239 <motion.img layoutId="hero" />1240 );1241}1242```12431244**Correct (native View Transition):**12451246```ts1247function openLightbox(img: HTMLImageElement) {1248 img.style.viewTransitionName = "hero";1249 document.startViewTransition(() => {1250 // Native browser transition1251 });1252}1253```12541255### 4.10 Style View Transition Pseudo-Elements12561257Style view transition pseudo-elements for custom animations.12581259**Incorrect (default crossfade only):**12601261```ts1262document.startViewTransition(() => { /* ... */ });1263```12641265**Correct (custom animation):**12661267```css1268::view-transition-group(card) {1269 animation-duration: 300ms;1270 animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);1271}1272```12731274### 4.11 Use ::backdrop for Dialog Backgrounds12751276Use ::backdrop pseudo-element for dialog/popover backgrounds.12771278**Incorrect (extra overlay node):**12791280```tsx1281<>1282 <div className={styles.overlay} onClick={close} />1283 <dialog className={styles.dialog}>{children}</dialog>1284</>1285```12861287**Correct (native ::backdrop):**12881289```css1290dialog::backdrop {1291 background: var(--black-a6);1292 backdrop-filter: blur(4px);1293}1294```12951296### 4.12 Use ::placeholder for Input Styling12971298Use ::placeholder for input placeholder styling, not wrapper elements.12991300**Incorrect (custom placeholder node):**13011302```tsx1303<div className={styles.inputWrapper}>1304 {!value && <span className={styles.placeholder}>Enter text...</span>}1305 <input value={value} />1306</div>1307```13081309**Correct (native ::placeholder):**13101311```css1312input::placeholder {1313 color: var(--gray-9);1314 opacity: 1;1315}1316```13171318### 4.13 Use ::selection for Text Styling13191320Use ::selection for text selection styling.13211322**Correct:**13231324```css1325::selection {1326 background: var(--blue-a5);1327 color: var(--gray-12);1328}1329```13301331### 4.14 Use ::marker for Custom List Bullets13321333Use ::marker to style list bullets without extra elements or background-image hacks.13341335**Incorrect (background image hack):**13361337```css1338li {1339 list-style: none;1340 background: url("bullet.svg") no-repeat 0 4px;1341 padding-left: 20px;1342}1343```13441345**Correct (native ::marker):**13461347```css1348li::marker {1349 color: var(--gray-8);1350 font-size: 0.8em;1351}1352```13531354### 4.15 Use ::first-line for Typographic Treatments13551356Use ::first-line for drop-cap-adjacent styling without JavaScript or hardcoded spans.13571358**Incorrect (manual span):**13591360```tsx1361<p>1362 <span className={styles["first-line"]}>The opening line</span>1363 is styled differently from the rest.1364</p>1365```13661367**Correct (native ::first-line):**13681369```css1370.article p:first-of-type::first-line {1371 font-variant-caps: small-caps;1372 font-weight: var(--font-weight-medium);1373}1374```13751376Reference: [MDN Pseudo-elements Reference](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Pseudo-elements), [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)13771378---13791380## 5. Audio Feedback13811382**Impact:** MEDIUM — When and how to use sound in UI, covering accessibility, appropriateness, and implementation.13831384### 5.1 Visual Equivalent for Every Sound13851386Every audio cue must have a visual equivalent; sound never replaces visual feedback.13871388**Incorrect (sound without visual):**13891390```tsx1391function SubmitButton({ onClick }) {1392 const handleClick = () => {1393 playSound("success");1394 onClick();1395 };1396}1397```13981399**Correct (sound with visual):**14001401```tsx1402function SubmitButton({ onClick }) {1403 const [status, setStatus] = useState("idle");14041405 const handleClick = () => {1406 playSound("success");1407 setStatus("success");1408 onClick();1409 };14101411 return <button data-status={status}>Submit</button>;1412}1413```14141415### 5.2 Toggle Setting to Disable Sounds14161417Provide explicit toggle to disable sounds in settings.14181419**Incorrect (no way to disable):**14201421```tsx1422function App() {1423 return <SoundProvider>{children}</SoundProvider>;1424}1425```14261427**Correct (toggle available):**14281429```tsx1430function App() {1431 const { soundEnabled } = usePreferences();1432 return (1433 <SoundProvider enabled={soundEnabled}>1434 {children}1435 </SoundProvider>1436 );1437}1438```14391440### 5.3 Respect prefers-reduced-motion for Sound14411442Respect prefers-reduced-motion as proxy for sound sensitivity.14431444**Incorrect (ignores preference):**14451446```tsx1447function playSound(name: string) {1448 audio.play();1449}1450```14511452**Correct (checks preference):**14531454```tsx1455function playSound(name: string) {1456 const prefersReducedMotion = window.matchMedia(1457 "(prefers-reduced-motion: reduce)"1458 ).matches;14591460 if (prefersReducedMotion) return;1461 audio.play();1462}1463```14641465### 5.4 Independent Volume Control14661467Allow volume adjustment independent of system volume.14681469**Incorrect (always full volume):**14701471```tsx1472function playSound() {1473 audio.volume = 1;1474 audio.play();1475}1476```14771478**Correct (user-controlled volume):**14791480```tsx1481function playSound() {1482 const { volume } = usePreferences();1483 audio.volume = volume;1484 audio.play();1485}1486```14871488### 5.5 No Sound on High-Frequency Interactions14891490Do not add sound to high-frequency interactions (typing, keyboard navigation).14911492**Incorrect (sound on every keystroke):**14931494```tsx1495function Input({ onChange }) {1496 const handleChange = (e) => {1497 playSound("keystroke");1498 onChange(e);1499 };1500}1501```15021503**Correct (no sound on typing):**15041505```tsx1506function Input({ onChange }) {1507 return <input onChange={onChange} />;1508}1509```15101511### 5.6 Sound for Confirmations15121513Sound is appropriate for confirmations: payments, uploads, form submissions.15141515**Correct:**15161517```tsx1518async function handlePayment() {1519 await processPayment();1520 playSound("success");1521 showConfirmation();1522}1523```15241525### 5.7 Sound for Errors and Warnings15261527Sound is appropriate for errors and warnings that can't be overlooked.15281529**Correct:**15301531```tsx1532function handleError(error: Error) {1533 playSound("error");1534 showErrorToast(error.message);1535}1536```15371538### 5.8 No Decorative Sound15391540Do not add sound to decorative moments with no informational value.15411542**Incorrect (hover sound):**15431544```tsx1545function Card({ onHover }) {1546 return (1547 <div onMouseEnter={() => playSound("hover")}>1548 {children}1549 </div>1550 );1551}1552```15531554### 5.9 Informative Not Punishing Sound15551556Sound should inform, not punish; avoid harsh sounds for user mistakes.15571558**Incorrect (harsh buzzer):**15591560```tsx1561function ValidationError() {1562 playSound("loud-buzzer");1563 return <span>Invalid input</span>;1564}1565```15661567**Correct (gentle alert):**15681569```tsx1570function ValidationError() {1571 playSound("gentle-alert");1572 return <span>Invalid input</span>;1573}1574```15751576### 5.10 Preload Audio Files15771578Preload audio files to avoid playback delay.15791580**Incorrect (loads on demand):**15811582```tsx1583function playSound(name: string) {1584 const audio = new Audio(`/sounds/${name}.mp3`);1585 audio.play();1586}1587```15881589**Correct (preloaded):**15901591```tsx1592const sounds = {1593 success: new Audio("/sounds/success.mp3"),1594 error: new Audio("/sounds/error.mp3"),1595};15961597Object.values(sounds).forEach(audio => audio.load());15981599function playSound(name: keyof typeof sounds) {1600 sounds[name].currentTime = 0;1601 sounds[name].play();1602}1603```16041605### 5.11 Subtle Default Volume16061607Default volume should be subtle, not loud.16081609**Incorrect (too loud):**16101611```tsx1612const DEFAULT_VOLUME = 1.0;1613```16141615**Correct (subtle):**16161617```tsx1618const DEFAULT_VOLUME = 0.3;1619```16201621### 5.12 Reset currentTime Before Replay16221623Reset audio currentTime before replay to allow rapid triggering.16241625**Incorrect (won't replay if playing):**16261627```tsx1628function playSound() {1629 audio.play();1630}1631```16321633**Correct (reset before play):**16341635```tsx1636function playSound() {1637 audio.currentTime = 0;1638 audio.play();1639}1640```16411642### 5.13 Match Sound Weight to Action16431644Sound weight should match action importance.16451646**Incorrect (fanfare for toggle):**16471648```tsx1649function handleToggle() {1650 playSound("triumphant-fanfare");1651 setEnabled(!enabled);1652}1653```16541655**Correct (weight matches action):**16561657```tsx1658function handleToggle() {1659 playSound("soft-click");1660 setEnabled(!enabled);1661}16621663function handlePurchase() {1664 playSound("success-chime");1665 completePurchase();1666}1667```16681669### 5.14 Sound Duration Matches Action Duration16701671Sound duration should match action duration.16721673**Incorrect (long sound for instant action):**16741675```tsx1676function handleClick() {1677 playSound("long-whoosh"); // 2000ms1678}1679```16801681**Correct (matched duration):**16821683```tsx1684function handleClick() {1685 playSound("click"); // 50ms1686}16871688function handleUpload() {1689 playSound("upload-progress"); // Matches upload duration1690}1691```16921693**Sound appropriateness matrix:**16941695| Interaction | Sound? | Reason |1696|-------------|--------|--------|1697| Payment success | Yes | Significant confirmation |1698| Form submission | Yes | User needs assurance |1699| Error state | Yes | Can't be overlooked |1700| Notification | Yes | May not be looking at screen |1701| Button click | Maybe | Only for significant buttons |1702| Typing | No | Too frequent |1703| Hover | No | Decorative only |1704| Scroll | No | Too frequent |1705| Navigation | No | Keyboard nav would be noisy |17061707Reference: [Web Audio API Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API), [prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)17081709---17101711## 6. Sound Synthesis17121713**Impact:** MEDIUM — Web Audio API best practices for procedural sound generation.17141715### 6.1 Reuse Single AudioContext17161717Reuse a single AudioContext instance; do not create new ones per sound.17181719**Incorrect (new context per call):**17201721```ts1722function playSound() {1723 const ctx = new AudioContext();1724}1725```17261727**Correct (singleton):**17281729```ts1730let audioContext: AudioContext | null = null;17311732function getAudioContext(): AudioContext {1733 if (!audioContext) {1734 audioContext = new AudioContext();1735 }1736 return audioContext;1737}1738```17391740### 6.2 Resume Suspended AudioContext17411742Check and resume suspended AudioContext before playing.17431744**Incorrect (plays without checking):**17451746```ts1747function playSound() {1748 const ctx = getAudioContext();1749}1750```17511752**Correct (resumes if suspended):**17531754```ts1755function playSound() {1756 const ctx = getAudioContext();1757 if (ctx.state === "suspended") {1758 ctx.resume();1759 }1760}1761```17621763### 6.3 Clean Up Audio Nodes After Playback17641765Disconnect and clean up audio nodes after playback.17661767**Incorrect (nodes remain connected):**17681769```ts1770source.start();1771```17721773**Correct (cleaned up on end):**17741775```ts1776source.start();1777source.onended = () => {1778 source.disconnect();1779 gain.disconnect();1780};1781```17821783### 6.4 Exponential Decay for Natural Sound17841785Use exponential ramps for natural decay, not linear.17861787**Incorrect (linear ramp):**17881789```ts1790gain.gain.linearRampToValueAtTime(0, t + 0.05);1791```17921793**Correct (exponential ramp):**17941795```ts1796gain.gain.exponentialRampToValueAtTime(0.001, t + 0.05);1797```17981799### 6.5 No Zero Target for Exponential Ramps18001801Exponential ramps cannot target 0; use 0.001 or similar small value.18021803**Incorrect (targets zero):**18041805```ts1806gain.gain.exponentialRampToValueAtTime(0, t + 0.05);1807```18081809**Correct (targets near-zero):**18101811```ts1812gain.gain.exponentialRampToValueAtTime(0.001, t + 0.05);1813```18141815### 6.6 Set Initial Value Before Ramp18161817Set initial value before ramping to avoid glitches.18181819**Incorrect (no initial value):**18201821```ts1822gain.gain.exponentialRampToValueAtTime(0.001, t + 0.05);1823```18241825**Correct (initial value set):**18261827```ts1828gain.gain.setValueAtTime(0.3, t);1829gain.gain.exponentialRampToValueAtTime(0.001, t + 0.05);1830```18311832### 6.7 Noise for Percussive Sounds18331834Use filtered noise for clicks/taps, not oscillators.18351836**Incorrect (oscillator for click):**18371838```ts1839const osc = ctx.createOscillator();1840osc.type = "sine";1841```18421843**Correct (noise burst for click):**18441845```ts1846const buffer = ctx.createBuffer(1, ctx.sampleRate * 0.008, ctx.sampleRate);1847const data = buffer.getChannelData(0);1848for (let i = 0; i < data.length; i++) {1849 data[i] = (Math.random() * 2 - 1) * Math.exp(-i / 50);1850}1851```18521853### 6.8 Oscillators for Tonal Sounds18541855Use oscillators with pitch movement for tonal sounds (pops, confirmations).18561857**Incorrect (static frequency):**18581859```ts1860osc.frequency.value = 400;1861```18621863**Correct (pitch sweep):**18641865```ts1866osc.frequency.setValueAtTime(400, t);1867osc.frequency.exponentialRampToValueAtTime(600, t + 0.04);1868```18691870### 6.9 Bandpass Filter for Sound Character18711872Apply bandpass filter to shape percussive sounds.18731874**Incorrect (raw noise):**18751876```ts1877source.connect(gain).connect(ctx.destination);1878```18791880**Correct (filtered noise):**18811882```ts1883const filter = ctx.createBiquadFilter();1884filter.type = "bandpass";1885filter.frequency.value = 4000;1886filter.Q.value = 3;1887source.connect(filter).connect(gain).connect(ctx.destination);1888```18891890### 6.10 Click Duration 5-15ms18911892Click/tap sounds should be 5-15ms duration.18931894**Incorrect (too long):**18951896```ts1897const buffer = ctx.createBuffer(1, ctx.sampleRate * 0.1, ctx.sampleRate);1898```18991900**Correct (appropriate duration):**19011902```ts1903const buffer = ctx.createBuffer(1, ctx.sampleRate * 0.008, ctx.sampleRate);1904```19051906### 6.11 Click Filter 3000-6000Hz19071908Bandpass filter for clicks should be 3000-6000Hz.19091910**Incorrect (too low):**19111912```ts1913filter.frequency.value = 500;1914```19151916**Correct (crisp range):**19171918```ts1919filter.frequency.value = 4000;1920```19211922### 6.12 Gain Under 1.019231924Gain values should not exceed 1.0 to prevent clipping.19251926**Incorrect (clipping):**19271928```ts1929gain.gain.setValueAtTime(1.5, t);1930```19311932**Correct (safe gain):**19331934```ts1935gain.gain.setValueAtTime(0.3, t);1936```19371938### 6.13 Filter Q Value 2-519391940Filter Q for clicks should be 2-5 for focused but not harsh sound.19411942**Incorrect (too resonant):**19431944```ts1945filter.Q.value = 15;1946```19471948**Correct (balanced Q):**19491950```ts1951filter.Q.value = 3;1952```19531954**Parameter translation table:**19551956| User Says | Parameter Change |1957|-----------|------------------|1958| "too harsh" | Lower filter frequency, reduce Q |1959| "too muffled" | Higher filter frequency |1960| "too long" | Shorter duration, faster decay |1961| "cuts off abruptly" | Use exponential decay |1962| "more mechanical" | Higher Q, faster decay |1963| "softer" | Lower gain, triangle wave |19641965Reference: [Web Audio API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)19661967---19681969## 7. Morphing Icons19701971**Impact:** LOW — Building icon components that morph between any two icons through SVG line transformation.19721973**Core concept:** Every icon is composed of exactly three SVG lines. Icons that need fewer lines collapse the extras to invisible center points. This constraint enables seamless morphing between any two icons.19741975**Architecture:**19761977```ts1978interface IconLine {1979 x1: number;1980 y1: number;1981 x2: number;1982 y2: number;1983 opacity?: number;1984}19851986interface IconDefinition {1987 lines: [IconLine, IconLine, IconLine];1988 rotation?: number;1989 group?: string;1990}19911992const CENTER = 7;1993const collapsed: IconLine = {1994 x1: CENTER, y1: CENTER, x2: CENTER, y2: CENTER, opacity: 0,1995};1996```19971998### 7.1 Icons Must Use Exactly Three Lines19992000Every icon MUST use exactly 3 lines. No more, no fewer.20012002**Incorrect (only 2 lines):**20032004```ts2005const checkIcon = {2006 lines: [2007 { x1: 2, y1: 7.5, x2: 5.5, y2: 11 },2008 { x1: 5.5, y1: 11, x2: 12, y2: 3 },2009 ],2010};2011```20122013**Correct (3 lines with collapsed):**20142015```ts2016const checkIcon = {2017 lines: [2018 { x1: 2, y1: 7.5, x2: 5.5, y2: 11 },2019 { x1: 5.5, y1: 11, x2: 12, y2: 3 },2020 collapsed,2021 ],2022};2023```20242025### 7.2 Use Collapsed Constant for Unused Lines20262027Unused lines must use the collapsed constant, not omission or null.20282029**Incorrect (null for unused):**20302031```ts2032const minusIcon = {2033 lines: [2034 { x1: 2, y1: 7, x2: 12, y2: 7 },2035 null,2036 null,2037 ],2038};2039```20402041**Correct (collapsed constant):**20422043```ts2044const minusIcon = {2045 lines: [2046 { x1: 2, y1: 7, x2: 12, y2: 7 },2047 collapsed,2048 collapsed,2049 ],2050};2051```20522053### 7.3 Consistent ViewBox Size20542055All icons must use the same viewBox (14x14 recommended).20562057**Incorrect (mixed scales):**20582059```ts2060const icon1 = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] }; // 14x142061const icon2 = { lines: [{ x1: 4, y1: 14, x2: 24, y2: 14 }, ...] }; // 28x282062```20632064**Correct (consistent scale):**20652066```ts2067const VIEWBOX_SIZE = 14;2068const CENTER = 7;2069```20702071### 7.4 Shared Group for Rotational Variants20722073Icons that are rotational variants MUST share the same group and base lines.20742075**Incorrect (different line definitions):**20762077```ts2078const arrowRight = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] };2079const arrowDown = { lines: [{ x1: 7, y1: 2, x2: 7, y2: 12 }, ...] };2080```20812082**Correct (shared base lines):**20832084```ts2085const arrowLines: [IconLine, IconLine, IconLine] = [2086 { x1: 2, y1: 7, x2: 12, y2: 7 },2087 { x1: 7.5, y1: 2.5, x2: 12, y2: 7 },2088 { x1: 7.5, y1: 11.5, x2: 12, y2: 7 },2089];20902091const icons = {2092 "arrow-right": { lines: arrowLines, rotation: 0, group: "arrow" },2093 "arrow-down": { lines: arrowLines, rotation: 90, group: "arrow" },2094 "arrow-left": { lines: arrowLines, rotation: 180, group: "arrow" },2095 "arrow-up": { lines: arrowLines, rotation: -90, group: "arrow" },2096};2097```20982099### 7.5 Spring Physics for Rotation21002101Rotation between grouped icons should use spring physics for natural motion.21022103**Incorrect (duration-based rotation):**21042105```tsx2106<motion.g animate={{ rotate: rotation }} transition={{ duration: 0.3 }} />2107```21082109**Correct (spring rotation):**21102111```tsx2112const rotation = useSpring(definition.rotation ?? 0, activeTransition);21132114<motion.g style={{ rotate: rotation, transformOrigin: "center" }} />2115```21162117### 7.6 Reduced Motion Support for Icons21182119Respect prefers-reduced-motion by disabling animations.21202121**Incorrect (always animates):**21222123```tsx2124function MorphingIcon({ icon }: Props) {2125 return <motion.line animate={...} transition={{ duration: 0.4 }} />;2126}2127```21282129**Correct (respects preference):**21302131```tsx2132function MorphingIcon({ icon }: Props) {2133 const reducedMotion = useReducedMotion() ?? false;2134 const activeTransition = reducedMotion ? { duration: 0 } : transition;21352136 return <motion.line animate={...} transition={activeTransition} />;2137}2138```21392140### 7.7 Instant Jump for Non-Grouped Icons21412142When transitioning between icons NOT in the same group, rotation should jump instantly.21432144**Incorrect (always animates rotation):**21452146```tsx2147useEffect(() => {2148 rotation.set(definition.rotation ?? 0);2149}, [definition]);2150```21512152**Correct (jumps when not grouped):**21532154```tsx2155useEffect(() => {2156 if (shouldRotate) {2157 rotation.set(definition.rotation ?? 0);2158 } else {2159 rotation.jump(definition.rotation ?? 0);2160 }2161}, [definition, shouldRotate]);2162```21632164### 7.8 Round Stroke Line Caps21652166Lines should use strokeLinecap="round" for polished endpoints.21672168**Incorrect (butt caps):**21692170```tsx2171<motion.line strokeLinecap="butt" />2172```21732174**Correct (round caps):**21752176```tsx2177<motion.line strokeLinecap="round" />2178```21792180### 7.9 Aria Hidden on Icon SVGs21812182Icon SVGs should be aria-hidden since they're decorative.21832184**Incorrect (no aria attribute):**21852186```tsx2187<svg width={size} height={size}>...</svg>2188```21892190**Correct (aria-hidden):**21912192```tsx2193<svg width={size} height={size} aria-hidden="true">...</svg>2194```21952196**Common icon patterns:**21972198```ts2199// Two-line icons (check, minus, chevron) — one collapsed line2200const check = {2201 lines: [2202 { x1: 2, y1: 7.5, x2: 5.5, y2: 11 },2203 { x1: 5.5, y1: 11, x2: 12, y2: 3 },2204 collapsed,2205 ],2206};22072208// Three-line icons (menu, asterisk) — all lines used2209const menu = {2210 lines: [2211 { x1: 2, y1: 3.5, x2: 12, y2: 3.5 },2212 { x1: 2, y1: 7, x2: 12, y2: 7 },2213 { x1: 2, y1: 10.5, x2: 12, y2: 10.5 },2214 ],2215};22162217// Point icons (more, grip) — zero-length lines as dots2218const more = {2219 lines: [2220 { x1: 3, y1: 7, x2: 3, y2: 7 },2221 { x1: 7, y1: 7, x2: 7, y2: 7 },2222 { x1: 11, y1: 7, x2: 11, y2: 7 },2223 ],2224};2225```22262227**Recommended transition:**22282229```ts2230const defaultTransition: Transition = {2231 ease: [0.19, 1, 0.22, 1],2232 duration: 0.4,2233};2234```22352236Reference: [Motion useSpring](https://motion.dev/docs/react-use-spring), [SVG Line Element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)22372238---22392240## 8. Container Animation22412242**Impact:** MEDIUM — Animating container width and height using a measure-and-animate pattern with ResizeObserver and Motion.22432244### 8.1 Two-Div Pattern for Animated Bounds22452246Use an outer animated div and an inner measured div. Never measure and animate the same element — it creates a feedback loop.22472248**Incorrect (measure and animate same element):**22492250```tsx2251function AnimatedContainer({ children }) {2252 const [ref, bounds] = useMeasure();2253 return (2254 <motion.div ref={ref} animate={{ height: bounds.height }}>2255 {children}2256 </motion.div>2257 );2258}2259```22602261**Correct (separate measure and animate targets):**22622263```tsx2264function AnimatedContainer({ children }) {2265 const [ref, bounds] = useMeasure();2266 return (2267 <motion.div animate={{ height: bounds.height }}>2268 <div ref={ref}>{children}</div>2269 </motion.div>2270 );2271}2272```22732274### 8.2 Guard Against Zero on Initial Render22752276On initial render, measured bounds are 0. Guard against this to prevent animating from 0 to actual size.22772278**Incorrect (animates from 0 on mount):**22792280```tsx2281<motion.div animate={{ width: bounds.width }}>2282 <div ref={ref}>{children}</div>2283</motion.div>2284```22852286**Correct (falls back to auto on first frame):**22872288```tsx2289<motion.div animate={{ width: bounds.width > 0 ? bounds.width : "auto" }}>2290 <div ref={ref}>{children}</div>2291</motion.div>2292```22932294### 8.3 Use ResizeObserver for Measurement22952296Use ResizeObserver to track element dimensions. It fires on resize without causing layout thrashing.22972298**Incorrect (measuring on every render):**22992300```tsx2301function useMeasure(ref) {2302 const [bounds, setBounds] = useState({ width: 0, height: 0 });2303 useEffect(() => {2304 if (ref.current) {2305 const rect = ref.current.getBoundingClientRect();2306 setBounds({ width: rect.width, height: rect.height });2307 }2308 });2309 return bounds;2310}2311```23122313**Correct (ResizeObserver):**23142315```tsx2316function useMeasure() {2317 const [element, setElement] = useState(null);2318 const [bounds, setBounds] = useState({ width: 0, height: 0 });2319 const ref = useCallback((node) => setElement(node), []);23202321 useEffect(() => {2322 if (!element) return;2323 const observer = new ResizeObserver(([entry]) => {2324 setBounds({2325 width: entry.contentRect.width,2326 height: entry.contentRect.height,2327 });2328 });2329 observer.observe(element);2330 return () => observer.disconnect();2331 }, [element]);23322333 return [ref, bounds];2334}2335```23362337### 8.4 Overflow Hidden on Animated Container23382339Set overflow: hidden on the animated outer container to clip content during size transitions.23402341**Incorrect (content overflows during animation):**23422343```tsx2344<motion.div animate={{ height: bounds.height }}>2345 <div ref={ref}>{children}</div>2346</motion.div>2347```23482349**Correct (clipped during transition):**23502351```tsx2352<motion.div animate={{ height: bounds.height }} style={{ overflow: "hidden" }}>2353 <div ref={ref}>{children}</div>2354</motion.div>2355```23562357### 8.5 Use Animated Bounds Sparingly23582359Animated bounds is a subtle effect. Reserve it for interactive elements where size changes are meaningful.23602361**Good use cases:** loading state buttons, expandable sections, accordions, FAQs, content reveals.23622363**Bad use cases:** every container on the page, static layouts, elements that don't change size.23642365### 8.6 Use Callback Ref for Measurement23662367Use a callback ref (not useRef) for measurement hooks so the observer attaches when the DOM node is ready.23682369**Incorrect (useRef may be null on first effect):**23702371```tsx2372const ref = useRef(null);2373useEffect(() => {2374 if (!ref.current) return;2375 observer.observe(ref.current);2376}, []);2377```23782379**Correct (callback ref guarantees node):**23802381```tsx2382const [element, setElement] = useState(null);2383const ref = useCallback((node) => setElement(node), []);2384useEffect(() => {2385 if (!element) return;2386 observer.observe(element);2387 return () => observer.disconnect();2388}, [element]);2389```23902391### 8.7 Add Delay for Natural Container Transitions23922393Add a small delay so the transition feels like it's catching up to the content.23942395**Correct:**23962397```tsx2398<motion.div2399 animate={{ height: bounds.height }}2400 transition={{ duration: 0.2, delay: 0.05 }}2401 style={{ overflow: "hidden" }}2402>2403 <div ref={ref}>{children}</div>2404</motion.div>2405```24062407Reference: [ResizeObserver - MDN](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver), [Motion Documentation](https://motion.dev)24082409---24102411## 9. Laws of UX24122413**Impact:** HIGH — Psychological principles behind interfaces that feel right. Violating these creates friction users can't articulate.24142415### 9.1 Size Interactive Targets for Easy Clicking24162417The bigger something is, the easier it is to click (Fitts's Law). Make interactive elements at least 32px.24182419**Incorrect (tiny click target):**24202421```css2422.icon-button {2423 width: 16px;2424 height: 16px;2425 padding: 0;2426}2427```24282429**Correct (comfortable target):**24302431```css2432.icon-button {2433 width: 32px;2434 height: 32px;2435 padding: 8px;2436}2437```24382439### 9.2 Expand Hit Areas with Invisible Padding24402441Use pseudo-elements or invisible padding to expand clickable areas beyond visible bounds.24422443**Incorrect (visible size equals hit area):**24442445```css2446.link {2447 font-size: 14px;2448}2449```24502451**Correct (expanded invisible hit area):**24522453```css2454.link {2455 position: relative;2456}24572458.link::before {2459 content: "";2460 position: absolute;2461 inset: -8px -12px;2462}2463```24642465### 9.3 Minimize Choices to Reduce Decision Time24662467Decision time increases logarithmically with the number of choices (Hick's Law). Use progressive disclosure.24682469**Incorrect (all options at once):**24702471```tsx2472function Settings() {2473 return (2474 <div>2475 {allSettings.map(setting => (2476 <SettingRow key={setting.id} {...setting} />2477 ))}2478 </div>2479 );2480}2481```24822483**Correct (progressive disclosure):**24842485```tsx2486function Settings() {2487 return (2488 <div>2489 {commonSettings.map(setting => (2490 <SettingRow key={setting.id} {...setting} />2491 ))}2492 <details>2493 <summary>Advanced</summary>2494 {advancedSettings.map(setting => (2495 <SettingRow key={setting.id} {...setting} />2496 ))}2497 </details>2498 </div>2499 );2500}2501```25022503### 9.4 Chunk Data into Groups of 5-925042505Working memory holds about 7 items (Miller's Law). Group and chunk large data sets for scannability.25062507**Incorrect (raw unformatted data):**25082509```tsx2510<span>4532015112830366</span>2511```25122513**Correct (chunked for readability):**25142515```tsx2516<span>4532 0151 1283 0366</span>2517```25182519### 9.5 Respond Within 400ms25202521Interactions must respond within 400ms to feel instant (Doherty Threshold). Above this, users notice delay.25222523**Incorrect (no feedback during loading):**25242525```tsx2526async function handleClick() {2527 const data = await fetchData();2528 setResult(data);2529}2530```25312532**Correct (immediate optimistic feedback):**25332534```tsx2535async function handleClick() {2536 setResult(optimisticData);2537 const data = await fetchData();2538 setResult(data);2539}2540```25412542### 9.6 Fake Speed When Actual Speed Isn't Possible25432544If you can't make something fast, make it feel fast with optimistic UI, skeletons, or progress indicators.25452546**Incorrect (blank screen during load):**25472548```tsx2549function Page() {2550 const { data, isLoading } = useFetch("/api/data");2551 if (isLoading) return null;2552 return <Content data={data} />;2553}2554```25552556**Correct (skeleton during load):**25572558```tsx2559function Page() {2560 const { data, isLoading } = useFetch("/api/data");2561 if (isLoading) return <Skeleton />;2562 return <Content data={data} />;2563}2564```25652566### 9.7 Accept Messy Input, Output Clean Data25672568Inputs should accept messy human data and normalize it (Postel's Law). Validate generously, format strictly.25692570**Incorrect (rigid format required):**25712572```tsx2573function DateInput({ onChange }) {2574 return (2575 <input2576 type="text"2577 placeholder="YYYY-MM-DD"2578 pattern="\d{4}-\d{2}-\d{2}"2579 onChange={onChange}2580 />2581 );2582}2583```25842585**Correct (accepts multiple formats):**25862587```tsx2588function DateInput({ onChange }) {2589 function handleChange(e) {2590 const parsed = parseFlexibleDate(e.target.value);2591 if (parsed) onChange(parsed);2592 }25932594 return (2595 <input2596 type="text"2597 placeholder="Any date format"2598 onChange={handleChange}2599 />2600 );2601}2602```26032604### 9.8 Show What Matters Now, Reveal Complexity Later26052606Don't overwhelm users with everything at once. Reveal complexity incrementally as needed.26072608**Incorrect (all controls visible):**26092610```tsx2611function Editor() {2612 return (2613 <div>2614 <BasicTools />2615 <AdvancedTools />2616 <ExpertTools />2617 <DebugTools />2618 </div>2619 );2620}2621```26222623**Correct (progressive disclosure):**26242625```tsx2626function Editor() {2627 const [showAdvanced, setShowAdvanced] = useState(false);2628 return (2629 <div>2630 <BasicTools />2631 {showAdvanced && <AdvancedTools />}2632 <button onClick={() => setShowAdvanced(!showAdvanced)}>2633 Toggle2634 </button>2635 </div>2636 );2637}2638```26392640### 9.9 Use Familiar UI Patterns26412642Users spend most of their time on other sites. They expect yours to work the same way (Jakob's Law).26432644**Incorrect (custom unconventional navigation):**26452646```tsx2647function Nav() {2648 return (2649 <nav>2650 <button onClick={() => navigate("/")}>⬡</button>2651 <button onClick={() => navigate("/search")}>⬢</button>2652 </nav>2653 );2654}2655```26562657**Correct (standard recognizable patterns):**26582659```tsx2660function Nav() {2661 return (2662 <nav>2663 <Link href="/">Home</Link>2664 <Link href="/search">Search</Link>2665 </nav>2666 );2667}2668```26692670### 9.10 Visual Polish Increases Perceived Usability26712672Users perceive aesthetically pleasing design as more usable. Small visual details compound into trust.26732674**Incorrect (unstyled, raw elements):**26752676```css2677.card {2678 border: 1px solid black;2679 padding: 10px;2680}2681```26822683**Correct (considered visual treatment):**26842685```css2686.card {2687 padding: 16px;2688 background: var(--gray-2);2689 border: 1px solid var(--gray-a4);2690 border-radius: 12px;2691 box-shadow: var(--shadow-1);2692}2693```26942695### 9.11 Group Related Elements Spatially26962697Elements near each other are perceived as related (Law of Proximity). Use spacing to create visual groups.26982699**Incorrect (uniform spacing between unrelated items):**27002701```css2702.form label,2703.form input,2704.form .hint,2705.form .divider {2706 margin-bottom: 16px;2707}2708```27092710**Correct (tighter spacing within groups, larger between):**27112712```css2713.form label {2714 margin-bottom: 4px;2715}27162717.form input {2718 margin-bottom: 2px;2719}27202721.form .hint {2722 margin-bottom: 24px;2723}2724```27252726### 9.12 Similar Elements Should Look Alike27272728Elements that function the same should look the same (Law of Similarity). Visual consistency signals functional consistency.27292730**Incorrect (same function, different appearance):**27312732```css2733.save-button {2734 background: blue;2735 border-radius: 8px;2736}27372738.submit-button {2739 background: green;2740 border-radius: 0;2741}2742```27432744**Correct (same function, same appearance):**27452746```css2747.primary-action {2748 background: var(--gray-12);2749 color: var(--gray-1);2750 border-radius: 8px;2751}2752```27532754### 9.13 Use Boundaries to Group Related Content27552756Elements sharing a clearly defined boundary are perceived as a group (Law of Common Region).27572758**Incorrect (flat list with no visual grouping):**27592760```tsx2761function Settings() {2762 return (2763 <div>2764 <Toggle label="Dark mode" />2765 <Toggle label="Notifications" />2766 <Input label="Email" />2767 <Input label="Password" />2768 </div>2769 );2770}2771```27722773**Correct (bounded sections):**27742775```tsx2776function Settings() {2777 return (2778 <div>2779 <section className={styles.group}>2780 <h3>Appearance</h3>2781 <Toggle label="Dark mode" />2782 </section>2783 <section className={styles.group}>2784 <h3>Account</h3>2785 <Input label="Email" />2786 <Input label="Password" />2787 </section>2788 </div>2789 );2790}2791```27922793### 9.14 Make Important Elements Visually Distinct27942795When multiple similar elements are present, the one that differs is most likely to be remembered (Von Restorff Effect).27962797**Incorrect (primary action blends in):**27982799```tsx2800<div className={styles.actions}>2801 <button className={styles.button}>Cancel</button>2802 <button className={styles.button}>Delete Account</button>2803</div>2804```28052806**Correct (destructive action stands out):**28072808```tsx2809<div className={styles.actions}>2810 <button className={styles["button-secondary"]}>Cancel</button>2811 <button className={styles["button-danger"]}>Delete Account</button>2812</div>2813```28142815### 9.15 Place Key Items First or Last28162817Users best remember the first and last items in a sequence (Serial Position Effect).28182819**Incorrect (important action buried in middle):**28202821```tsx2822<nav>2823 <Link href="/settings">Settings</Link>2824 <Link href="/">Home</Link>2825 <Link href="/about">About</Link>2826</nav>2827```28282829**Correct (key items at edges):**28302831```tsx2832<nav>2833 <Link href="/">Home</Link>2834 <Link href="/about">About</Link>2835 <Link href="/settings">Settings</Link>2836</nav>2837```28382839### 9.16 End Experiences with Clear Success States28402841People judge experiences by their peak moment and their end (Peak-End Rule). Invest in completion states.28422843**Incorrect (abrupt end after action):**28442845```tsx2846async function handleSubmit() {2847 await submitForm(data);2848 router.push("/");2849}2850```28512852**Correct (satisfying completion state):**28532854```tsx2855async function handleSubmit() {2856 await submitForm(data);2857 setStatus("success");2858}28592860return status === "success" ? (2861 <SuccessScreen message="You're all set." />2862) : (2863 <Form onSubmit={handleSubmit} />2864);2865```28662867### 9.17 Move Complexity to the System28682869Every system has irreducible complexity (Tesler's Law). The question is who handles it — the user or the system.28702871**Incorrect (complexity pushed to user):**28722873```tsx2874<input2875 type="text"2876 placeholder="Enter date as YYYY-MM-DDTHH:mm:ss.sssZ"2877/>2878```28792880**Correct (system absorbs complexity):**28812882```tsx2883<DatePicker2884 onChange={(date) => setDate(date.toISOString())}2885/>2886```28872888### 9.18 Show Progress Toward Completion28892890People accelerate behavior as they approach a goal (Goal-Gradient Effect). Show how close they are.28912892**Incorrect (no sense of progress):**28932894```tsx2895function Onboarding({ step }) {2896 return <OnboardingStep step={step} />;2897}2898```28992900**Correct (progress visible):**29012902```tsx2903function Onboarding({ step, totalSteps }) {2904 return (2905 <div>2906 <ProgressBar value={step} max={totalSteps} />2907 <span>Step {step} of {totalSteps}</span>2908 <OnboardingStep step={step} />2909 </div>2910 );2911}2912```29132914### 9.19 Show Incomplete State to Drive Completion29152916People remember incomplete tasks better than completed ones (Zeigarnik Effect).29172918**Incorrect (no indication of incomplete profile):**29192920```tsx2921function Dashboard() {2922 return <DashboardContent />;2923}2924```29252926**Correct (incomplete state visible):**29272928```tsx2929function Dashboard({ profile }) {2930 return (2931 <div>2932 {!profile.isComplete && (2933 <Banner>2934 Complete your profile — {profile.completionPercent}% done2935 </Banner>2936 )}2937 <DashboardContent />2938 </div>2939 );2940}2941```29422943### 9.20 Simplify Complex Visuals into Clear Forms29442945People interpret complex visuals as the simplest form possible (Law of Pragnanz). Reduce visual noise.29462947**Incorrect (visually noisy layout):**29482949```css2950.card {2951 border: 2px dashed red;2952 background: linear-gradient(45deg, #f0f, #0ff);2953 box-shadow: 5px 5px 0 black, 10px 10px 0 gray;2954 outline: 3px dotted blue;2955}2956```29572958**Correct (clear, simple form):**29592960```css2961.card {2962 background: var(--gray-2);2963 border: 1px solid var(--gray-a4);2964 border-radius: 12px;2965 box-shadow: var(--shadow-1);2966}2967```29682969### 9.21 Prioritize the Critical 20% of Features2970297180% of users use 20% of features (Pareto Principle). Optimize the critical path first.29722973**Incorrect (all features equally prominent):**29742975```tsx2976function Toolbar() {2977 return (2978 <div>2979 {allFeatures.map(f => <Button key={f.id}>{f.label}</Button>)}2980 </div>2981 );2982}2983```29842985**Correct (critical features prominent, rest accessible):**29862987```tsx2988function Toolbar() {2989 return (2990 <div>2991 {criticalFeatures.map(f => <Button key={f.id}>{f.label}</Button>)}2992 <MoreMenu features={secondaryFeatures} />2993 </div>2994 );2995}2996```29972998### 9.22 Minimize Extraneous Cognitive Load29993000Remove anything that doesn't help the user complete their task. Decoration, redundant labels, and unnecessary options all add load.30013002**Incorrect (extraneous elements):**30033004```tsx3005function DeleteDialog() {3006 return (3007 <dialog>3008 <Icon name="warning" size={64} />3009 <h2>Warning!</h2>3010 <p>Are you absolutely sure you want to delete?</p>3011 <p>This action is permanent and cannot be undone.</p>3012 <p>All associated data will be lost forever.</p>3013 <div>3014 <button>Cancel</button>3015 <button>Delete</button>3016 <button>Learn More</button>3017 </div>3018 </dialog>3019 );3020}3021```30223023**Correct (essential information only):**30243025```tsx3026function DeleteDialog() {3027 return (3028 <dialog>3029 <h2>Delete this item?</h2>3030 <p>This can't be undone.</p>3031 <div>3032 <button>Cancel</button>3033 <button>Delete</button>3034 </div>3035 </dialog>3036 );3037}3038```30393040### 9.23 Visually Connect Related Elements30413042Elements that are visually connected (by lines, color, or frames) are perceived as more related (Law of Uniform Connectedness).30433044**Incorrect (steps with no visual connection):**30453046```tsx3047function Steps({ current }) {3048 return (3049 <div>3050 <span>Step 1</span>3051 <span>Step 2</span>3052 <span>Step 3</span>3053 </div>3054 );3055}3056```30573058**Correct (connected with a visual line):**30593060```tsx3061function Steps({ current }) {3062 return (3063 <div className={styles.steps}>3064 {steps.map((step, i) => (3065 <div key={step.id} className={styles.step} data-active={i <= current}>3066 <div className={styles.dot} />3067 {i < steps.length - 1 && <div className={styles.connector} />}3068 <span>{step.label}</span>3069 </div>3070 ))}3071 </div>3072 );3073}3074```30753076Reference: [Laws of UX](https://lawsofux.com/) by Jon Yablonski30773078---30793080## 10. Predictive Prefetching30813082**Impact:** MEDIUM — Loading content before the user clicks by analyzing cursor trajectory, reducing perceived latency by 100-200ms.30833084### 10.1 Trajectory Prediction Over Hover Prefetching30853086Hover prefetching starts too late. Trajectory prediction fires while the cursor is still in motion, reclaiming 100-200ms.30873088**Incorrect (waits for hover):**30893090```tsx3091<Link3092 href="/about"3093 onMouseEnter={() => router.prefetch("/about")}3094>3095 About3096</Link>3097```30983099**Correct (trajectory-based):**31003101```tsx3102const { elementRef } = useForesight({3103 callback: () => router.prefetch("/about"),3104 hitSlop: 20,3105 name: "about-link",3106});31073108<Link ref={elementRef} href="/about">About</Link>3109```31103111### 10.2 Prefetch by Intent, Not Viewport31123113Don't prefetch everything visible in the viewport. Prefetch based on user intent to avoid wasted bandwidth.31143115**Incorrect (prefetch all visible links):**31163117```tsx3118<Link href="/page" prefetch={true}>Page</Link>3119```31203121**Correct (intent-based prefetching):**31223123```tsx3124<Link href="/page" prefetch={false}>Page</Link>3125```31263127### 10.3 Use hitSlop to Trigger Predictions Earlier31283129Expand the invisible prediction area around elements with hitSlop to start loading sooner.31303131**Incorrect (tight prediction area):**31323133```tsx3134const { elementRef } = useForesight({3135 callback: () => prefetch(),3136 hitSlop: 0,3137});3138```31393140**Correct (expanded prediction area):**31413142```tsx3143const { elementRef } = useForesight({3144 callback: () => prefetch(),3145 hitSlop: 20,3146});3147```31483149### 10.4 Fall Back Gracefully on Touch Devices31503151Touch devices have no cursor. Fall back to viewport or touch-start strategies automatically.31523153**Incorrect (assumes cursor exists):**31543155```tsx3156function PrefetchLink({ href, children }) {3157 return (3158 <Link3159 href={href}3160 onMouseMove={() => prefetch(href)}3161 >3162 {children}3163 </Link>3164 );3165}3166```31673168**Correct (device-aware strategy):**31693170```tsx3171const { elementRef } = useForesight({3172 callback: () => router.prefetch(href),3173 hitSlop: 20,3174});3175```31763177### 10.5 Prefetch on Keyboard Navigation31783179Monitor focus changes and prefetch when the user is a few tab stops away from a registered element.31803181**Correct (tab-aware prefetching):**31823183```tsx3184const { elementRef } = useForesight({3185 callback: () => router.prefetch("/settings"),3186 name: "settings-link",3187});3188```31893190### 10.6 Use Predictive Prefetching Selectively31913192Predictive prefetching doesn't belong in every project. Use it where navigation latency is noticeable.31933194**Good use cases:** data-heavy dashboards, multi-page apps with slow API responses, e-commerce product pages.31953196**Bad use cases:** static sites with instant navigation, single-page apps with all data preloaded.31973198Reference: [ForesightJS](https://foresightjs.com), [Next.js Prefetching Docs](https://nextjs.org/docs/app/guides/prefetching)31993200---32013202## 11. Typography32033204**Impact:** MEDIUM — CSS font and text properties most developers overlook. The difference between typographically considered and not.32053206### 11.1 Tabular Numbers for Data Display32073208Use tabular-nums for any numeric data that should align in columns.32093210**Incorrect (proportional numbers misalign):**32113212```css3213.price { font-variant-numeric: proportional-nums; }3214```32153216**Correct (tabular numbers align):**32173218```css3219.price { font-variant-numeric: tabular-nums; }3220```32213222### 11.2 Oldstyle Numbers for Body Text32233224Use oldstyle-nums in body text so numbers blend with lowercase letters. Use lining-nums in tables and headings.32253226**Correct (prose):**32273228```css3229.body-text { font-variant-numeric: oldstyle-nums; }3230```32313232**Correct (data):**32333234```css3235.data-table { font-variant-numeric: lining-nums tabular-nums; }3236```32373238### 11.3 Slashed Zero for Disambiguation32393240Enable slashed zero in code-adjacent UIs so users never confuse 0 with O.32413242**Correct:**32433244```css3245.code { font-variant-numeric: slashed-zero; }3246```32473248### 11.4 Enable Contextual Alternates32493250Keep contextual alternates (calt) enabled. They adjust punctuation and glyph shapes based on surrounding characters.32513252**Correct (usually on by default — don't disable):**32533254```css3255body { font-feature-settings: "calt" 1; }3256```32573258### 11.5 Use Disambiguation Stylistic Set for UI32593260Enable ss02 (or your font's disambiguation set) in code-facing UIs to distinguish I, l, 1 and 0, O.32613262**Correct:**32633264```css3265.code-ui { font-feature-settings: "ss02"; }3266```32673268### 11.6 Keep Optical Sizing Auto32693270Leave font-optical-sizing at auto. The font adjusts glyph shapes for the current size — thicker strokes at small sizes, finer details at large sizes.32713272**Incorrect (forced off):**32733274```css3275body { font-optical-sizing: none; }3276```32773278**Correct (automatic adjustment):**32793280```css3281body { font-optical-sizing: auto; }3282```32833284### 11.7 Use Antialiased Font Smoothing32853286Set -webkit-font-smoothing: antialiased on retina displays. Default subpixel rendering looks thicker and fuzzier.32873288**Correct:**32893290```css3291body {3292 -webkit-font-smoothing: antialiased;3293 -moz-osx-font-smoothing: grayscale;3294}3295```32963297### 11.8 Balance Headings with text-wrap32983299Use text-wrap: balance on headings to make lines roughly equal length instead of one long line and a short orphan.33003301**Incorrect (unbalanced heading):**33023303```css3304h1 { /* default text-wrap */ }3305```33063307**Correct (balanced):**33083309```css3310h1 { text-wrap: balance; }3311```33123313### 11.9 Offset Underlines from Descenders33143315Use text-underline-offset to push underlines below descenders so they look intentional.33163317**Incorrect (underline collides with descenders):**33183319```css3320a { text-decoration: underline; }3321```33223323**Correct (offset underline):**33243325```css3326a {3327 text-decoration: underline;3328 text-underline-offset: 3px;3329 text-decoration-skip-ink: auto;3330}3331```33323333### 11.10 Disable Font Synthesis for Missing Styles33343335Set font-synthesis: none to prevent the browser from faking bold or italic. Browser-generated faux styles look terrible.33363337**Correct:**33383339```css3340.icon-font,3341.display-font {3342 font-synthesis: none;3343}3344```33453346**Typography quick reference:**33473348| Property | Use Case | Value |3349|----------|----------|-------|3350| `font-variant-numeric: tabular-nums` | Data tables, pricing | Fixed-width digits |3351| `font-variant-numeric: oldstyle-nums` | Body text | Blends with lowercase |3352| `font-variant-numeric: slashed-zero` | Code UIs | Distinguishes 0 from O |3353| `font-feature-settings: "ss02"` | Code UIs | Disambiguates I/l/1 |3354| `font-optical-sizing: auto` | Everywhere | Size-adaptive glyphs |3355| `-webkit-font-smoothing: antialiased` | Retina displays | Thinner, cleaner text |3356| `text-wrap: balance` | Headings | Even line lengths |3357| `text-underline-offset: 3px` | Links | Clear descender space |3358| `font-synthesis: none` | Display/icon fonts | Prevents faux styles |33593360### 11.11 Use font-display swap33613362Set font-display: swap so text renders immediately with a fallback while the custom font loads.33633364**Correct:**33653366```css3367@font-face {3368 font-family: "Inter";3369 src: url("/fonts/inter.woff2") format("woff2");3370 font-display: swap;3371}3372```33733374### 11.12 Continuous Weight Values with Variable Fonts33753376Variable fonts accept any integer from 100-900, not just standard stops.33773378**Correct (precise weight):**33793380```css3381.medium { font-weight: 450; }3382.semibold { font-weight: 550; }3383```33843385### 11.13 text-wrap pretty for Body Text33863387Use text-wrap: pretty for body text to reduce orphans. Use balance for headings.33883389**Correct:**33903391```css3392p { text-wrap: pretty; }3393h1, h2, h3 { text-wrap: balance; }3394```33953396### 11.14 Pair Justified Text with Hyphens33973398Justified text without hyphens creates rivers of whitespace.33993400**Incorrect (rivers):**34013402```css3403.article { text-align: justify; }3404```34053406**Correct (hyphenation prevents rivers):**34073408```css3409.article {3410 text-align: justify;3411 hyphens: auto;3412}3413```34143415### 11.15 Add Letter Spacing to Uppercase Text34163417Uppercase and small-caps text needs positive letter-spacing to feel open and readable.34183419**Incorrect (tight uppercase):**34203421```css3422.label {3423 text-transform: uppercase;3424 font-size: 12px;3425}3426```34273428**Correct (opened up):**34293430```css3431.label {3432 text-transform: uppercase;3433 font-size: 12px;3434 letter-spacing: 0.05em;3435}3436```34373438### 11.16 Use Typographic Fractions34393440Enable diagonal-fractions to convert 1/2, 1/3 into proper typographic fractions.34413442**Correct:**34433444```css3445.recipe { font-variant-numeric: diagonal-fractions; }3446```34473448Reference: [Inter Typeface](https://rsms.me/inter/), [MDN font-feature-settings](https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings), [MDN font-variant-numeric](https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant-numeric)34493450---34513452## 12. Visual Design34533454**Impact:** HIGH — CSS design fundamentals that compound into visual polish. Small details that separate considered interfaces from default ones.34553456### 12.1 Concentric Border Radius for Nested Elements34573458When nesting rounded elements, inner radius must equal outer radius minus the gap. Same radius on both creates uneven curves.34593460**Incorrect (same radius on both):**34613462```css3463.outer {3464 border-radius: 16px;3465 padding: 8px;3466}34673468.inner {3469 border-radius: 16px;3470}3471```34723473**Correct (concentric radius):**34743475```css3476.outer {3477 --padding: 8px;3478 --inner-radius: 8px;34793480 border-radius: calc(var(--inner-radius) + var(--padding));3481 padding: var(--padding);3482}34833484.inner {3485 border-radius: var(--inner-radius);3486}3487```34883489### 12.2 Layer Multiple Shadows for Realistic Depth34903491A single box-shadow looks flat. Layer multiple shadows with increasing blur and decreasing opacity to mimic real light.34923493**Incorrect (single flat shadow):**34943495```css3496.card {3497 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);3498}3499```35003501**Correct (layered shadows):**35023503```css3504.card {3505 box-shadow:3506 0 1px 2px rgba(0, 0, 0, 0.06),3507 0 4px 8px rgba(0, 0, 0, 0.04),3508 0 12px 24px rgba(0, 0, 0, 0.03);3509}3510```35113512### 12.3 Consistent Shadow Direction Across UI35133514All shadows must share the same offset direction to imply a single light source. Mixed directions feel broken.35153516**Incorrect (conflicting light sources):**35173518```css3519.card { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); }3520.modal { box-shadow: 4px 0 8px rgba(0, 0, 0, 0.1); }3521.tooltip { box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.1); }3522```35233524**Correct (consistent top-down light):**35253526```css3527.card { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); }3528.modal { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); }3529.tooltip { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); }3530```35313532### 12.4 Use Neutral Colors for Shadows35333534Pure black shadows look harsh. Use deep neutrals or semi-transparent dark colors.35353536**Incorrect (pure black):**35373538```css3539.card {3540 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);3541}3542```35433544**Correct (neutral shadow):**35453546```css3547.card {3548 box-shadow: 0 4px 12px rgba(17, 24, 39, 0.08);3549}3550```35513552### 12.5 Shadow Size Indicates Elevation35533554Larger blur and offset means higher elevation. Use a consistent shadow scale.35553556**Correct (elevation scale):**35573558```css3559:root {3560 --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.05);3561 --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.08);3562 --shadow-3: 0 8px 24px rgba(0, 0, 0, 0.12);3563}35643565.card { box-shadow: var(--shadow-1); }3566.dropdown { box-shadow: var(--shadow-2); }3567.modal { box-shadow: var(--shadow-3); }3568```35693570### 12.6 Animate Shadows via Pseudo-Element Opacity35713572Transitioning box-shadow directly forces expensive repaints. Animate opacity on a pseudo-element instead.35733574**Incorrect (animating box-shadow):**35753576```css3577.card {3578 box-shadow: var(--shadow-1);3579 transition: box-shadow 0.2s ease;3580}3581.card:hover {3582 box-shadow: var(--shadow-3);3583}3584```35853586**Correct (pseudo-element opacity):**35873588```css3589.card {3590 position: relative;3591 box-shadow: var(--shadow-1);3592}3593.card::after {3594 content: "";3595 position: absolute;3596 inset: 0;3597 border-radius: inherit;3598 box-shadow: var(--shadow-3);3599 opacity: 0;3600 transition: opacity 0.2s ease;3601 pointer-events: none;3602 z-index: -1;3603}3604.card:hover::after {3605 opacity: 1;3606}3607```36083609### 12.7 Use a Consistent Spacing Scale36103611Don't use arbitrary pixel values. Define a scale and use it throughout.36123613**Incorrect (arbitrary values):**36143615```css3616.header { padding: 17px; }3617.card { margin-bottom: 13px; }3618.section { gap: 22px; }3619```36203621**Correct (consistent scale):**36223623```css3624:root {3625 --space-1: 4px;3626 --space-2: 8px;3627 --space-3: 12px;3628 --space-4: 16px;3629 --space-5: 24px;3630 --space-6: 32px;3631 --space-7: 48px;3632}36333634.header { padding: var(--space-4); }3635.card { margin-bottom: var(--space-3); }3636.section { gap: var(--space-5); }3637```36383639### 12.8 Use Semi-Transparent Borders36403641Semi-transparent borders adapt to any background color and create subtle, non-jarring separation.36423643**Incorrect (hardcoded border color):**36443645```css3646.card {3647 border: 1px solid #e5e5e5;3648}3649```36503651**Correct (alpha border):**36523653```css3654.card {3655 border: 1px solid var(--gray-a4);3656}3657```36583659### 12.9 Full Shadow Anatomy on Buttons36603661A polished button uses six layered techniques, not just a single box-shadow:366236631. **Outer cut shadow** — 0.5px dark box-shadow to "cut" the button into the surface36642. **Inner ambient highlight** — 1px inset box-shadow on all sides for environmental light reflections36653. **Inner top highlight** — 1px inset top highlight for the primary light source from above36664. **Layered depth shadows** — At least 3 external shadows for natural lighting36675. **Text drop-shadow** — Drop-shadow on text/icons for better contrast against the button background36686. **Subtle gradient background** — If you can tell there's a gradient, it's too much36693670**Incorrect (flat button):**36713672```css3673.button {3674 background: var(--gray-12);3675 color: var(--gray-1);3676 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);3677}3678```36793680**Correct (full shadow anatomy):**36813682```css3683.button {3684 background: linear-gradient(3685 to bottom,3686 color-mix(in srgb, var(--gray-12) 100%, white 4%),3687 var(--gray-12)3688 );3689 color: var(--gray-1);3690 box-shadow:3691 0 0 0 0.5px rgba(0, 0, 0, 0.3),3692 inset 0 0 0 1px rgba(255, 255, 255, 0.04),3693 inset 0 1px 0 rgba(255, 255, 255, 0.07),3694 0 1px 2px rgba(0, 0, 0, 0.1),3695 0 2px 4px rgba(0, 0, 0, 0.06),3696 0 4px 8px rgba(0, 0, 0, 0.03);3697 text-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);3698}3699```37003701Reference: [Designing Beautiful Shadows in CSS](https://www.joshwcomeau.com/css/designing-shadows/), [Concentric Border Radius](https://jakub.kr/work/concentric-border-radius), [@PixelJanitor](https://threadreaderapp.com/thread/1623358514440859649)37023703---37043705## Output Format37063707When reviewing files, output findings as:37083709```3710file:line - [rule-id] description of issue37113712Example:3713components/modal/index.tsx:45 - [timing-under-300ms] Exit animation 400ms exceeds 300ms limit3714components/button/styles.module.css:12 - [physics-active-state] Missing :active transform3715components/drawer/index.tsx:23 - [spring-for-gestures] Drag interaction using easing instead of spring3716```37173718## Summary Table37193720After findings, output a summary:37213722| Rule | Count | Severity |3723|------|-------|----------|3724| `timing-under-300ms` | 2 | HIGH |3725| `physics-active-state` | 3 | MEDIUM |3726| `exit-requires-wrapper` | 1 | HIGH |37273728## References37293730- [The Illusion of Life: Disney Animation](https://www.amazon.com/Illusion-Life-Disney-Animation/dp/0786860707)3731- [Apple WWDC23: Animate with Springs](https://developer.apple.com/videos/play/wwdc2023/10158)3732- [Motion Documentation](https://motion.dev)3733- [The Beauty of Bezier Curves - Freya Holmer](https://www.youtube.com/watch?v=aVwxzDHniEw)3734- [MDN Pseudo-elements Reference](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Pseudo-elements)3735- [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)3736- [Web Audio API Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)3737- [prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)3738- [SVG Line Element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)3739- [ResizeObserver - MDN](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)3740- [Laws of UX](https://lawsofux.com/) by Jon Yablonski3741- [ForesightJS](https://foresightjs.com)3742- [Next.js Prefetching Docs](https://nextjs.org/docs/app/guides/prefetching)3743- [Inter Typeface](https://rsms.me/inter/)3744- [MDN font-feature-settings](https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings)3745- [MDN font-variant-numeric](https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant-numeric)3746- [Designing Beautiful Shadows in CSS - Josh W. Comeau](https://www.joshwcomeau.com/css/designing-shadows/)3747- [Concentric Border Radius](https://jakub.kr/work/concentric-border-radius)3748- [Nested Rounded Corners](https://www.ondrejkonecny.com/blog/nested-rounded-corners/)3749- [MDN text-wrap](https://developer.mozilla.org/en-US/docs/Web/CSS/text-wrap)3750
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| vllm-project/vllmAGENTS.md · 88k | AGENTS.md | setuptestlint-formatstyle+5 | 100/100 | 3 days ago | |
| OnlyTerp/prompt-cache-skillsAGENTS.md · 112 | AGENTS.md | setupbuildtestlint-format+5 | 100/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199k | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| unoplat/unoplat-code-confluenceunoplat-code-confluence-frontend/AGENTS.md · 95 | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 2 days ago | |
| SkeneTechnologies/skene-cookbookAGENTS.md · 51 | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 2 days ago | |
| netdata/netdatasrc/go/plugin/ibm.d/AGENTS.md · 80k | AGENTS.md | buildtestlint-formatarch+3 | 99/100 | 3 days ago | |
| unoplat/unoplat-code-confluenceunoplat-code-confluence-query-engine/AGENTS.md · 95 | AGENTS.md | setupbuildtestlint-format+5 | 98/100 | 2 days ago | |
| alibaba/opc-starterAGENTS.md · 87 | AGENTS.md | setupbuildtestlint-format+5 | 97/100 | 3 days ago |
