返回 AiToEarn
interaction-design.md
根目录 / project / aitoearn-web / .agents / skills / impeccable / reference / interaction-design.md
1 # Interaction Design
2
3 ## The Eight Interactive States
4
5 Every interactive element needs these states designed:
6
7 | State | When | Visual Treatment |
8 |-------|------|------------------|
9 | **Default** | At rest | Base styling |
10 | **Hover** | Pointer over (not touch) | Subtle lift, color shift |
11 | **Focus** | Keyboard/programmatic focus | Visible ring (see below) |
12 | **Active** | Being pressed | Pressed in, darker |
13 | **Disabled** | Not interactive | Reduced opacity, no pointer |
14 | **Loading** | Processing | Spinner, skeleton |
15 | **Error** | Invalid state | Red border, icon, message |
16 | **Success** | Completed | Green check, confirmation |
17
18 **The common miss**: Designing hover without focus, or vice versa. They're different. Keyboard users never see hover states.
19
20 ## Focus Rings: Do Them Right
21
22 **Never `outline: none` without replacement.** It's an accessibility violation. Instead, use `:focus-visible` to show focus only for keyboard users:
23
24 ```css
25 /* Hide focus ring for mouse/touch */
26 button:focus {
27 outline: none;
28 }
29
30 /* Show focus ring for keyboard */
31 button:focus-visible {
32 outline: 2px solid var(--color-accent);
33 outline-offset: 2px;
34 }
35 ```
36
37 **Focus ring design**:
38 - High contrast (3:1 minimum against adjacent colors)
39 - 2-3px thick
40 - Offset from element (not inside it)
41 - Consistent across all interactive elements
42
43 ## Form Design: The Non-Obvious
44
45 **Placeholders aren't labels.** They disappear on input. Always use visible `<label>` elements. **Validate on blur**, not on every keystroke (exception: password strength). Place errors **below** fields with `aria-describedby` connecting them.
46
47 ## Loading States
48
49 **Optimistic updates**: Show success immediately, rollback on failure. Use for low-stakes actions (likes, follows), not payments or destructive actions. **Skeleton screens > spinners**: they preview content shape and feel faster than generic spinners.
50
51 ## Modals: The Inert Approach
52
53 Focus trapping in modals used to require complex JavaScript. Now use the `inert` attribute:
54
55 ```html
56 <!-- When modal is open -->
57 <main inert>
58 <!-- Content behind modal can't be focused or clicked -->
59 </main>
60 <dialog open>
61 <h2>Modal Title</h2>
62 <!-- Focus stays inside modal -->
63 </dialog>
64 ```
65
66 Or use the native `<dialog>` element:
67
68 ```javascript
69 const dialog = document.querySelector('dialog');
70 dialog.showModal(); // Opens with focus trap, closes on Escape
71 ```
72
73 ## The Popover API
74
75 For tooltips, dropdowns, and non-modal overlays, use native popovers:
76
77 ```html
78 <button popovertarget="menu">Open menu</button>
79 <div id="menu" popover>
80 <button>Option 1</button>
81 <button>Option 2</button>
82 </div>
83 ```
84
85 **Benefits**: Light-dismiss (click outside closes), proper stacking, no z-index wars, accessible by default.
86
87 ## Dropdown & Overlay Positioning
88
89 Dropdowns rendered with `position: absolute` inside a container that has `overflow: hidden` or `overflow: auto` will be clipped. This is the single most common dropdown bug in generated code.
90
91 ### CSS Anchor Positioning
92
93 The modern solution uses the CSS Anchor Positioning API to tether an overlay to its trigger without JavaScript:
94
95 ```css
96 .trigger {
97 anchor-name: --menu-trigger;
98 }
99
100 .dropdown {
101 position: fixed;
102 position-anchor: --menu-trigger;
103 position-area: block-end span-inline-end;
104 margin-top: 4px;
105 }
106
107 /* Flip above if no room below */
108 @position-try --flip-above {
109 position-area: block-start span-inline-end;
110 margin-bottom: 4px;
111 }
112 ```
113
114 Because the dropdown uses `position: fixed`, it escapes any `overflow` clipping on ancestor elements. The `@position-try` block handles viewport edges automatically. **Browser support**: Chrome 125+, Edge 125+. Not yet in Firefox or Safari - use a fallback for those browsers.
115
116 ### Popover + Anchor Combo
117
118 Combining the Popover API with anchor positioning gives you stacking, light-dismiss, accessibility, and correct positioning in one pattern:
119
120 ```html
121 <button popovertarget="menu" class="trigger">Open</button>
122 <div id="menu" popover class="dropdown">
123 <button>Option 1</button>
124 <button>Option 2</button>
125 </div>
126 ```
127
128 The `popover` attribute places the element in the **top layer**, which sits above all other content regardless of z-index or overflow. No portal needed.
129
130 ### Portal / Teleport Pattern
131
132 In component frameworks, render the dropdown at the document root and position it with JavaScript:
133
134 - **React**: `createPortal(dropdown, document.body)`
135 - **Vue**: `<Teleport to="body">`
136 - **Svelte**: Use a portal library or mount to `document.body`
137
138 Calculate position from the trigger's `getBoundingClientRect()`, then apply `position: fixed` with `top` and `left` values. Recalculate on scroll and resize.
139
140 ### Fixed Positioning Fallback
141
142 For browsers without anchor positioning support, `position: fixed` with manual coordinates avoids overflow clipping:
143
144 ```css
145 .dropdown {
146 position: fixed;
147 /* top/left set via JS from trigger's getBoundingClientRect() */
148 }
149 ```
150
151 Check viewport boundaries before rendering. If the dropdown would overflow the bottom edge, flip it above the trigger. If it would overflow the right edge, align it to the trigger's right side instead.
152
153 ## Destructive Actions: Undo > Confirm
154
155 **Undo is better than confirmation dialogs.** Users click through confirmations mindlessly. Remove from UI immediately, show undo toast, actually delete after toast expires. Use confirmation only for truly irreversible actions (account deletion), high-cost actions, or batch operations.
156
157 ## Keyboard Navigation Patterns
158
159 ### Roving Tabindex
160
161 For component groups (tabs, menu items, radio groups), one item is tabbable; arrow keys move within:
162
163 ```html
164 <div role="tablist">
165 <button role="tab" tabindex="0">Tab 1</button>
166 <button role="tab" tabindex="-1">Tab 2</button>
167 <button role="tab" tabindex="-1">Tab 3</button>
168 </div>
169 ```
170
171 Arrow keys move `tabindex="0"` between items. Tab moves to the next component entirely.
172
173 ### Skip Links
174
175 Provide skip links (`<a href="#main-content">Skip to main content</a>`) for keyboard users to jump past navigation. Hide off-screen, show on focus.
176
177 ## Gesture Discoverability
178
179 Swipe-to-delete and similar gestures are invisible. Hint at their existence:
180
181 - **Partially reveal**: Show delete button peeking from edge
182 - **Onboarding**: Coach marks on first use
183 - **Alternative**: Always provide a visible fallback (menu with "Delete")
184
185 Don't rely on gestures as the only way to perform actions.
186
187 ---
188
189 **Avoid**: Removing focus indicators without alternatives. Using placeholder text as labels. Touch targets <44x44px. Generic error messages. Custom controls without ARIA/keyboard support.
190
190 lines MARKDOWN