Interaction
State model, hit targets, response timing, feedback levels, destructive actions, hide versus disable, forms, keyboard, tooltips, bulk selection, drag and drop, copy, navigation and touch gestures.
Input methods
Pointer, touch and keyboard are equal. Hover effects live only inside @media (hover: hover); touch devices never depend on hover to reveal information (checkboxes and drag handles that appear on hover are always visible on touch).
State model
Every interactive element defines these states.
| State | Optical meaning | Appearance | Timing |
|---|---|---|---|
| Rest | Unlit | As specified | — |
| Hover | Catches light | Cards +1 level, up 1px, specular light; controls get --state-hover (ink 5%, dark 7%); primary button → brand-hover | Fade 150ms |
| Pressed | Pressed toward the table | Scale 0.97 (cards 0.99), −1 level, --state-press (ink 9%, dark 12%) | Snappy |
| Focus-visible | A beam | 2px brand ring, offset 2px, converging from outside; keyboard focus only | Snappy |
| Selected | Light passes through | brand-tint fill + on-brand-tint text + check; refracts in from the click point | Snappy |
| Current | A lit slab | Current sidebar item: surface + elev-1 + brand icon + weight 600 | — |
| Dragged | Lifted toward the light | Level 3, scale 1.02, grabbing cursor | Smooth |
| Disabled | Opaque | Opacity 0.40; no hover or press; cursor: not-allowed; the reason must be visible (tooltip) | — |
| Loading | Light sweeping | Spinner only after 300ms; once shown, at least 400ms; button keeps its width, label hidden | — |
| Invalid | A red band | danger 2px ring + message below the field + icon | Fade |
| Read-only | Transparent | No container fill, text in ink-2 | — |
Hit targets
| Case | Minimum |
|---|---|
| Pointer | 32 × 32 |
| Touch | 44 × 44 (visually smaller elements get a transparent hit area) |
| Spacing between targets | 8 |
Response timing
| Duration | Feedback |
|---|---|
| < 100ms | Immediate state change, no loading indicator |
| 100ms – 1s | Nothing for the first 300ms; then an inline spinner at the trigger, shown for at least 400ms to avoid flicker |
| 1 – 10s | Skeleton (light sweep) on first load; a progress bar when progress is known |
| > 10s | Move to a background task: a "Moved to background" toast + task center + an inbox notification when done |
Small reversible actions (toggles, mark as read, reorder, favorite) update optimistically: change the UI at once, roll back on failure and explain why in a toast.
Feedback levels
From light to heavy: in the field (validation text) < status text ("Saved 10:32") < page banner (quota, announcements, permissions) < toast (action result, dismisses itself) < dialog (a decision is needed; blocking). Pick the lightest level that says it clearly, and use only one level for one event.
Destructive actions
| Kind | Approach |
|---|---|
| Reversible (delete a draft, remove from a group, archive) | Do it immediately + a toast with Undo, 6s, timer paused while the pointer is over it |
| Irreversible (permanently delete an article, revoke access) | Confirmation dialog: the title names the object; the body states the consequence; danger button on the right, Cancel on the left |
| High impact (delete a site, delete a company, clear a cluster) | Confirmation dialog + the danger button enables only after the object's name is typed |
Hide or disable
| Situation | Approach |
|---|---|
| No permission | Hide (useCan()) |
| Temporarily unavailable (quota used up, state doesn't allow it) | Disable + a tooltip with the reason and how to fix it |
| Form incomplete or invalid | Don't disable submit; on click, show the errors and focus the first one |
Forms
- Validate a field on blur, and only after it has been edited; once it has an error, re-check as the user types and clear the error as soon as it's fixed.
- On submit, validate every field, then scroll to and focus the first error.
- Put the message under the field and say how to fix it ("A domain can't contain spaces"), never just "Invalid format".
- With unsaved changes, a frosted save bar (level 3) rises from the bottom: "Unsaved changes [Discard] [Save]"; confirm before leaving the page.
- Editors autosave with a 2s debounce, with the status in the top bar: "Saving…" → "Saved 10:32"; on failure the status turns danger and offers Retry.
Keyboard
| Scope | Keys | Action |
|---|---|---|
| Global | ⌘K / Ctrl K | Search and command palette |
| Global | / | Focus the current list's search box |
| Global | Esc | Close the topmost layer (tooltip → menu → dialog → drawer) |
| Global | ? | Keyboard shortcut help |
| List | ↑ / ↓ | Move the current row |
| List | Enter | Open the current row |
| List | Space | Select / deselect the current row |
| List | Shift + click | Select a range |
| Tabs / segmented | ← / → | Switch |
| Menu | First letter | Jump to a matching item |
| Dialog | — | Focus the first field on open; trap focus inside; return focus to the trigger on close |
Tab order matches visual order; a "Skip to main content" link is the first focus stop on every page.
Tooltips
- Only for icon-only buttons, truncated text and disabled reasons. Never put interactive content in a tooltip.
- Appears after the pointer rests 500ms; appears immediately when moving to a neighbour within 300ms; disappears immediately on leave; appears immediately on keyboard focus; Esc closes it.
- Inverse surface (
inversefill +on-inversetext),r-xs, caption size, max width 260.
Selection and bulk actions
- Desktop: checkboxes appear on row hover (always visible once anything is selected); touch: always visible.
- With ≥ 1 item selected, a frosted bulk bar (level 3,
r-full) rises at the bottom center: "3 selected · [Actions…] · Clear"; count changes use the bouncy spring. - Select-all selects the current page only, and the bulk bar offers "Select all 128".
Drag and drop
- A handle (
grip-vertical) appears on hover; on touch, long-press 500ms to lift. - Lifted: level 3, scale 1.02; other items make room with the smooth spring; the drop position shows as a
brand-tintplaceholder. - Auto-scroll within 48px of the container's top and bottom edges.
- Keyboard alternative: the row menu's Move up / Move down / Move to top.
Copy
After copying, the icon turns into a check and the label reads "Copied" for 1.5s; when the clipboard is unavailable, select the text and say "Press ⌘C to copy".
Navigation and URLs
- Filters, sort, tab, page and any open drawer are in the URL, so refreshing and sharing keep the state.
- Going back restores scroll position and filters.
- Navigation always uses real links (they can open in a new tab); external links carry a
↗icon and open in a new tab.
Touch gestures (compact class)
| Gesture | Threshold | Result |
|---|---|---|
| Direction lock | 8px from the start | Lock to horizontal or vertical to avoid mis-touches |
| Swipe right from the left edge | Starts ≤ 20px from the edge | Opens the navigation drawer |
| Close a drawer / bottom sheet | Moved > 30%, or release velocity > 0.5px/ms | Closes; otherwise springs back (smooth) |
| Release point | Judge by the velocity-projected end point (deceleration 0.998/ms) | Not where the finger stopped, but where the motion would land |
| Long press | 500ms | Opens the row menu (or starts a drag) |
| Pull to refresh | Not used | Use the refresh button in the header |
Empty, error and loading
| State | Appearance |
|---|---|
| First load | A skeleton shaped like the final layout (light sweep), never a full-page spinner |
| Refresh | Keep the old data, show a spinner only at the trigger; cross-fade in the new data |
| Empty (never had data) | The empty template: 64 icon tile + title + explanation + primary action |
| Empty (no filter results) | One sentence + "Clear filters", no large icon |
| Error | A danger icon tile + the reason + "Retry" inside the card; a full-page message only when the whole page fails |
| No permission | Name the role needed and who to contact; never show a 403 code |
Layout
A 4px base with an 8px rhythm, four size classes, shell dimensions, seven page templates, three densities, responsive tables and the website layout.
Motion
Four spring presets (snappy, smooth, gentle, bouncy) compiled to CSS linear(), fade and exit, choreography, six signature motions, the transition catalogue, reduced motion and performance.