Skip to content
ThorbisInterface Guidelines (opens in a new tab)

Interactions

One focused chapter from the Thorbis interface research. 37 rules. Read the research index for every chapter and focused study.

Interactions

  • Keyboard works everywhere. All flows are keyboard-operable & follow the WAI-ARIA Authoring Patterns (opens in a new tab). In Thorbis this includes dispatch boards, schedule timelines, record sheets, and command surfaces, not just forms.
    Morning board
    Tab in. Arrows move. Enter opens.
  • Clear focus. Every focusable element shows a visible focus ring. Prefer :focus-visible over :focus to avoid distracting pointer users. Set :focus-within for grouped controls. Focus and selection are visually distinct.
    Focus vs selected
    The ring is focus. The fill is selection. They are not the same.
  • Manage focus. Use focus traps, move & return focus according to the WAI-ARIA Patterns (opens in a new tab). The kit we ship is the shadcn Dialog. Native <dialog> is fine underneath. On close, return focus to the trigger.
    Job card
    Close the card. You land back on the row that opened it.
  • Match visual & hit targets. If it looks clickable, it is clickable across its whole visual extent. Exception: if the visual target is under 24px, expand its hit target to at least 24px. Desktop prefers 40px. On mobile, the minimum size is 44px. Never let extended hit areas overlap. A checkbox and its label share one target. If part of a control looks live, it is live.
    Full row hit

    The tap is the row, not the word.

    If it looks like a row, the whole row is the target.
  • Mobile input size. <input> font size is ≥ 16px on mobile to prevent iOS Safari auto-zoom. Never set maximum-scale=1 or user-scalable=no.
    Customer phone
    16px on a phone. Safari will not jump the page.
  • Respect zoom. Never disable browser zoom. The page must work at 200% zoom and reflow at 320px width.
  • Hydration-safe inputs. Inputs must not lose focus or value after hydration.
    Customer phone
    Type. Hydrate. The letters stay.
  • Don’t block paste. Never disable paste in <input> or <textarea>. Dispatchers paste job numbers, phone numbers, and one-time codes.
    Customer phone
    Paste the number from the text. It takes.
  • Loading buttons. Show a loading indicator & keep the original label. “Save” with a spinner, not a bare spinner.
    Dispatch
    The mark morphs to a loader and turns. The word stays in one hole.
  • State icons morph. Any icon that changes state goes through Morphicons (opens in a new tab). Idle becomes the next glyph with a snappy spring. Still icons stay Phosphor Regular at 16. Do not cross-fade two SVGs.
    Dispatch
    The mark morphs to a loader and turns. The word stays in one hole.
  • A loading icon turns. Busy morphs to a loader, then the loader spins. Honor prefers-reduced-motion: morph becomes a swap, the spin stops. The word stays. See Loading buttons and Nothing shifts.
    Dispatch
    The mark morphs to a loader and turns. The word stays in one hole.
  • Minimum loading-state duration. If you show a spinner/skeleton, add a short show-delay (~150–300 ms) & a minimum visible time (~300–500 ms) to avoid flicker on fast responses.
    Dispatch
    The mark morphs to a loader and turns. The word stays in one hole.
  • URL as state. Persist filters, tabs, pagination, expanded panels, and the open record sheet in the URL so share, refresh, and Back/Forward work. Anytime useState describes a destination, it belongs in the URL. If it is a place, a customer, a job, an estimate, an invoice, it has an address.

    /jobs/114-oak

    The row has an address. Refresh keeps you here.
  • Optimistic updates. Update the UI immediately when success is likely; reconcile on server response. On failure, show an error & roll back or provide Undo.
    On my way
    The switch flips first. The server can catch up.
  • Ellipsis for further input & loading states. Menu options that open a follow-up e.g., “Rename…” & loading states e.g., “Saving…”, “Dispatching…” end with an ellipsis character (…, not ...).

    Search jobs…

    The ellipsis means we need more. A period would lie.
  • Confirm destructive actions. Require confirmation or provide Undo with a safe window. Money, payroll, and customer-facing sends are never hover-only.
    Invoice 1842
    Destroy waits for a second yes. The hole does not jump.
  • Prevent double-tap zoom on controls. Set touch-action: manipulation on interactive elements.
  • Tap highlight follows design. Set -webkit-tap-highlight-color to match the surface, not the default gray flash.
    Collect
    The press is the feedback. No flash of blue from the OS.
  • Design forgiving interactions. Controls minimize finickiness with generous hit targets, clear affordances, & predictable interactions. A dispatcher in a rush should not miss.
  • Tooltip timing. Delay the first tooltip in a group; subsequent peers have no delay. Tooltips never own essential information.
  • Overscroll behavior. Set overscroll-behavior: contain on modals, drawers, and record sheets.
  • Scroll positions persist. Back/Forward restores prior scroll. Forward navigation starts at the top.
  • Autofocus for speed. On desktop screens with a single primary input, autofocus. Rarely autofocus on mobile because the keyboard opening can cause layout shift.
    Customer phone
    The field is ready. They can type.
  • No dead zones. Same law as Match visual & hit targets.
    Full row hit

    The tap is the row, not the word.

    If it looks like a row, the whole row is the target.
  • Deep-link everything. Same law as URL as state.

    /jobs/114-oak

    The row has an address. Refresh keeps you here.
  • Clean drag interactions. Disable text selection & apply inert while an element is dragged so selection and hover don’t fire at the same time. Dragging always has a non-drag alternative.
  • Links are links. Use <a> for navigation so Cmd/Ctrl+Click, middle-click, and “open in new tab” work. Never substitute a <button> or <div> for a destination. Buttons do actions; links go places.
    A job is a real href. Cmd-click opens a tab.
  • Announce async updates. Use a polite live region (role="status") for toasts, result counts, and “Saved”. Reserve role="alert" for urgent errors not tied to a field.

    Idle.

    The live region tells the screen reader the job dispatched.
  • Locale-aware keyboard shortcuts. Internationalize shortcuts for non-QWERTY layouts. Show platform-specific symbols (⌘ vs Ctrl).
    Command
    ⌘K is the accelerator. The path is also on the page.
  • Respond on the first frame. Rauno Freiberg : truly fluid gestures are immediately responsive. Scale, press, and drag apply on pointer-down, not after a threshold animation plays out. If the user reverses, the surface follows. Don’t make them wait for an entrance to finish before they can swipe it back. Invisible Details of Interaction Design (opens in a new tab).
    Collect
    The press is the feedback. No flash of blue from the OS.
  • Reuse metaphors. Don’t invent gestures. Rauno: great interaction design rewards learning by reusing metaphors. A sheet is a layer, swipe it away. A peek is a card, it comes from the row. Pinch is precision. Don’t teach a third swipe direction because this view is “special.”
  • Commit destructive swipes on release. Lightweight reveals (peek a sheet, show search) may fire during the swipe, once the surface reaches its logical position. Destructive or hard-to-undo actions. dismiss a job, archive an invoice, send, wait for the finger to lift. The operator can change their mind mid-gesture. Rauno, “Swipe Gestures.”
    Invoice 1842
    Destroy waits for a second yes. The hole does not jump.
  • Motion names the source. A record sheet grows from the row that opened it. An inspector slides from the edge it lives on. A popover scales from its trigger. If three music apps sat on one row, you’d launch from the icon, not the island, so the relationship is obvious. Rauno, “Spatial Consistency.”
    Job card
    Close the card. You land back on the row that opened it.
  • Context is an input. The mother of all inputs is no input. Command Center already knows the open job. A tech in the van sees field actions first. A pass or QR can raise brightness without asking. Don’t make the operator restate what the surface can see. Rauno, “Implicit Input.”

    Press to peek. Hover stays on a mouse.

    No hover preview on a thumb. Peek is a press.
  • Closer and bigger is faster. Fitts’s Law: time to hit a target is distance and size. Edges and corners are infinite, put frequent chrome there. A row action that lives next to the pointer beats one that lives in a distant toolbar. Radial menus are the pure case; we rarely need one. We do need the law. Rauno via Paul Fitts.
    The close control kisses the corner. The thumb already lives there.
  • Scroll stays on the pane you started. Wheel or trackpad momentum does not hop to the inspector, the sidebar, or a newly hovered list. Focus change cancels the previous scroll. A Magic Mouse gets this right; a hijacked second pane is a daily defect. Rauno, “Scrolling.”
  • Reveal without reflow. Sidebars and inspectors slide with transform and a compensating negative margin so inner content does not skew. Paco Coursey documented the trick from Linear. macOS Windows (opens in a new tab). Don’t animate width to open a panel.

I built this for the dispatcher at 7 a.m., the tech in a crawlspace, and the owner closing the books. Quiet chrome. Precise work. I’m still learning. These are the notes.

Applied bookmark research

Reviewed 2026-09-06. These examples reinforce the existing owners; they do not introduce another visual style or claim the product behavior is implemented. Read the 26-source review and access limits.

A control keeps its meaning through every state

A button that changes width, a field that loses input, or a dialog that loses focus breaks the task even when the resting design looks precise.

Review the approved primitive as a sequence: resting, focused, activated, pending, resolved, and recovered. Keep the action label and footprint stable, associate errors with their fields, and preserve a visible keyboard path.

Thorbis example. Save estimate remains Save estimate while its state mark changes. A validation error keeps the entered values, explains the repair beside the field, and moves focus only when the interaction calls for it.

Verify. Test Tab, Shift-Tab, Enter, Escape where applicable, paste, invalid input, rapid activation, and a slow response. Repeat at 390px, in both themes, with zoom and reduced motion.

Do not import. No hand-built duplicate primitive, hidden label, disabled browser zoom, or borrowed shadow-only outer edge that violates D-017.

Open the ui skill · Evidence: Web Interface Guidelines; Invisible Details; components.build; Details that make interfaces feel better

Transfer the intent, not the desktop gesture

An interaction that works with a precise pointer or modifier key may be unusable for a tech holding a phone.

Preserve the record and task while adapting the visible action path to the device. Use the existing native owner for target sizing, safe areas, focus, and host behavior; no critical action relies only on hover or a hidden gesture.

Thorbis example. A field photo remains attached to the same job when the tech changes orientation or returns from another app. The visible capture action and retry path remain available without discovering a desktop shortcut.

Verify. Check one-handed use, software keyboard opening, text enlargement, orientation, focus visibility, and reduced motion on the relevant device. Keep permission refusal and return-from-interruption states in the review.

Do not import. No desktop hover contract on a phone, blocked zoom, silent clipboard access, or unverified claim that a web imitation is native behavior.

Open the native skill · Evidence: Web Interface Guidelines; Design Engineer Principles; Devouring Details

Loading search…

Loading search. You can close this message and keep reading.