124 lines
4.1 KiB
Markdown
124 lines
4.1 KiB
Markdown
# Interaction Design
|
|
|
|
## The Eight Interactive States
|
|
|
|
Every interactive element needs these states designed:
|
|
|
|
| State | When | Visual Treatment |
|
|
|-------|------|------------------|
|
|
| **Default** | At rest | Base styling |
|
|
| **Hover** | Pointer over (not touch) | Subtle lift, color shift |
|
|
| **Focus** | Keyboard/programmatic focus | Visible ring (see below) |
|
|
| **Active** | Being pressed | Pressed in, darker |
|
|
| **Disabled** | Not interactive | Reduced opacity, no pointer |
|
|
| **Loading** | Processing | Spinner, skeleton |
|
|
| **Error** | Invalid state | Red border, icon, message |
|
|
| **Success** | Completed | Green check, confirmation |
|
|
|
|
**The common miss**: Designing hover without focus, or vice versa. They're different. Keyboard users never see hover states.
|
|
|
|
## Focus Rings: Do Them Right
|
|
|
|
**Never `outline: none` without replacement.** It's an accessibility violation. Instead, use `:focus-visible` to show focus only for keyboard users:
|
|
|
|
```css
|
|
/* Hide focus ring for mouse/touch */
|
|
button:focus {
|
|
outline: none;
|
|
}
|
|
|
|
/* Show focus ring for keyboard */
|
|
button:focus-visible {
|
|
outline: 2px solid var(--color-accent);
|
|
outline-offset: 2px;
|
|
}
|
|
```
|
|
|
|
**Focus ring design**:
|
|
- High contrast (3:1 minimum against adjacent colors)
|
|
- 2-3px thick
|
|
- Offset from element (not inside it)
|
|
- Consistent across all interactive elements
|
|
|
|
## Form Design: The Non-Obvious
|
|
|
|
**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.
|
|
|
|
## Loading States
|
|
|
|
**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.
|
|
|
|
## Modals: The Inert Approach
|
|
|
|
Focus trapping in modals used to require complex JavaScript. Now use the `inert` attribute:
|
|
|
|
```html
|
|
<!-- When modal is open -->
|
|
<main inert>
|
|
<!-- Content behind modal can't be focused or clicked -->
|
|
</main>
|
|
<dialog open>
|
|
<h2>Modal Title</h2>
|
|
<!-- Focus stays inside modal -->
|
|
</dialog>
|
|
```
|
|
|
|
Or use the native `<dialog>` element:
|
|
|
|
```javascript
|
|
const dialog = document.querySelector('dialog');
|
|
dialog.showModal(); // Opens with focus trap, closes on Escape
|
|
```
|
|
|
|
## The Popover API
|
|
|
|
For tooltips, dropdowns, and non-modal overlays, use native popovers:
|
|
|
|
```html
|
|
<button popovertarget="menu">Open menu</button>
|
|
<div id="menu" popover>
|
|
<button>Option 1</button>
|
|
<button>Option 2</button>
|
|
</div>
|
|
```
|
|
|
|
**Benefits**: Light-dismiss (click outside closes), proper stacking, no z-index wars, accessible by default.
|
|
|
|
## Destructive Actions: Undo > Confirm
|
|
|
|
**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.
|
|
|
|
## Keyboard Navigation Patterns
|
|
|
|
### Roving Tabindex
|
|
|
|
For component groups (tabs, menu items, radio groups), one item is tabbable; arrow keys move within:
|
|
|
|
```html
|
|
<div role="tablist">
|
|
<button role="tab" tabindex="0">Tab 1</button>
|
|
<button role="tab" tabindex="-1">Tab 2</button>
|
|
<button role="tab" tabindex="-1">Tab 3</button>
|
|
</div>
|
|
```
|
|
|
|
Arrow keys move `tabindex="0"` between items. Tab moves to the next component entirely.
|
|
|
|
### Skip Links
|
|
|
|
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.
|
|
|
|
## Gesture Discoverability
|
|
|
|
Swipe-to-delete and similar gestures are invisible. Hint at their existence:
|
|
|
|
- **Partially reveal**: Show delete button peeking from edge
|
|
- **Onboarding**: Coach marks on first use
|
|
- **Alternative**: Always provide a visible fallback (menu with "Delete")
|
|
|
|
Don't rely on gestures as the only way to perform actions.
|
|
|
|
---
|
|
|
|
**Avoid**: Removing focus indicators without alternatives. Using placeholder text as labels. Touch targets <44x44px. Generic error messages. Custom controls without ARIA/keyboard support.
|