Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
One-time setup that gathers your project's design context and saves it to CLAUDE.md for future sessions.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/interaction-design.md
1# Interaction Design23## The Eight Interactive States45Every interactive element needs these states designed:67| 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 |1718**The common miss**: Designing hover without focus, or vice versa. They're different. Keyboard users never see hover states.1920## Focus Rings: Do Them Right2122**Never `outline: none` without replacement.** It's an accessibility violation. Instead, use `:focus-visible` to show focus only for keyboard users:2324```css25/* Hide focus ring for mouse/touch */26button:focus {27outline: none;28}2930/* Show focus ring for keyboard */31button:focus-visible {32outline: 2px solid var(--color-accent);33outline-offset: 2px;34}35```3637**Focus ring design**:38- High contrast (3:1 minimum against adjacent colors)39- 2-3px thick40- Offset from element (not inside it)41- Consistent across all interactive elements4243## Form Design: The Non-Obvious4445**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.4647## Loading States4849**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.5051## Modals: The Inert Approach5253Focus trapping in modals used to require complex JavaScript. Now use the `inert` attribute:5455```html56<!-- 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```6566Or use the native `<dialog>` element:6768```javascript69const dialog = document.querySelector('dialog');70dialog.showModal(); // Opens with focus trap, closes on Escape71```7273## The Popover API7475For tooltips, dropdowns, and non-modal overlays, use native popovers:7677```html78<button popovertarget="menu">Open menu</button>79<div id="menu" popover>80<button>Option 1</button>81<button>Option 2</button>82</div>83```8485**Benefits**: Light-dismiss (click outside closes), proper stacking, no z-index wars, accessible by default.8687## Dropdown & Overlay Positioning8889Dropdowns 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.9091### CSS Anchor Positioning9293The modern solution uses the CSS Anchor Positioning API to tether an overlay to its trigger without JavaScript:9495```css96.trigger {97anchor-name: --menu-trigger;98}99100.dropdown {101position: fixed;102position-anchor: --menu-trigger;103position-area: block-end span-inline-end;104margin-top: 4px;105}106107/* Flip above if no room below */108@position-try --flip-above {109position-area: block-start span-inline-end;110margin-bottom: 4px;111}112```113114Because 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.115116### Popover + Anchor Combo117118Combining the Popover API with anchor positioning gives you stacking, light-dismiss, accessibility, and correct positioning in one pattern:119120```html121<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```127128The `popover` attribute places the element in the **top layer**, which sits above all other content regardless of z-index or overflow. No portal needed.129130### Portal / Teleport Pattern131132In component frameworks, render the dropdown at the document root and position it with JavaScript:133134- **React**: `createPortal(dropdown, document.body)`135- **Vue**: `<Teleport to="body">`136- **Svelte**: Use a portal library or mount to `document.body`137138Calculate position from the trigger's `getBoundingClientRect()`, then apply `position: fixed` with `top` and `left` values. Recalculate on scroll and resize.139140### Fixed Positioning Fallback141142For browsers without anchor positioning support, `position: fixed` with manual coordinates avoids overflow clipping:143144```css145.dropdown {146position: fixed;147/* top/left set via JS from trigger's getBoundingClientRect() */148}149```150151Check 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.152153### Anti-Patterns154155- **`position: absolute` inside `overflow: hidden`** - The dropdown will be clipped. Use `position: fixed` or the top layer instead.156- **Arbitrary z-index values** like `z-index: 9999` - Use a semantic z-index scale: `dropdown (100) -> sticky (200) -> modal-backdrop (300) -> modal (400) -> toast (500) -> tooltip (600)`.157- **Rendering dropdown markup inline** without an escape hatch from the parent's stacking context. Either use `popover` (top layer), a portal, or `position: fixed`.158159## Destructive Actions: Undo > Confirm160161**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.162163## Keyboard Navigation Patterns164165### Roving Tabindex166167For component groups (tabs, menu items, radio groups), one item is tabbable; arrow keys move within:168169```html170<div role="tablist">171<button role="tab" tabindex="0">Tab 1</button>172<button role="tab" tabindex="-1">Tab 2</button>173<button role="tab" tabindex="-1">Tab 3</button>174</div>175```176177Arrow keys move `tabindex="0"` between items. Tab moves to the next component entirely.178179### Skip Links180181Provide skip links (`<a href="#main-content">Skip to main content</a>`) for keyboard users to jump past navigation. Hide off-screen, show on focus.182183## Gesture Discoverability184185Swipe-to-delete and similar gestures are invisible. Hint at their existence:186187- **Partially reveal**: Show delete button peeking from edge188- **Onboarding**: Coach marks on first use189- **Alternative**: Always provide a visible fallback (menu with "Delete")190191Don't rely on gestures as the only way to perform actions.192193---194195**Avoid**: Removing focus indicators without alternatives. Using placeholder text as labels. Touch targets <44x44px. Generic error messages. Custom controls without ARIA/keyboard support.196