Interaction Details

10 min

Interaction Details

The difference between an interface that works and one that feels right often comes down to interaction details—focus management, hit target sizing, hover behaviour on touch devices. These are the decisions that keyboard users, touch users, and users with motor impairments encounter on every interaction. Get them right and the app feels guided. Get them wrong and users miss buttons, lose their place, and fight the browser.

Focus management: trap and restore

When a dialog, menu, or sheet opens, two things must happen. First, Tab must cycle focus within the overlay—not escape to the page behind it. This is focus trapping. Second, when the overlay closes, focus must return to the element that opened it. Without this, the user lands at the top of the document and has to Tab back to where they were.

<Dialog open onClose={close}>
  <Dialog.Content>
    <input autoFocus />
    <button>Save</button>
    <button onClick={close}>Cancel</button>
  </Dialog.Content>
</Dialog>

The native <dialog> element handles focus trapping automatically. Headless component libraries—Radix, Aria, Headless UI—handle both trapping and restoration. If you're building custom overlays, verify both behaviours explicitly by watching document.activeElement as you keyboard-navigate.

Other moments that need focus attention:

  • First focusable on open. A dialog that opens with focus on the close button frustrates the user. Focus the first form field or the primary action.
  • Inline error focus. When form validation fails on submit, focus the first invalid field. The user lands on the problem; their next keystroke fixes it.
  • After deletion. When a list item is deleted, focus the next item (or the previous if it was last). Don't drop focus to <body>.
Focus Trap Visualizer
Page behind

Subscribe for updates

Tab through the controls to see focus flow.

Focus log

Click into the modal and start tabbing.

Trap on — Tab cycles within the modal, Shift+Tab cycles backward.

Hit targets: 24px desktop, 44px mobile

Hit target sizing is the minimum dimensions of an interactive element that a user can reliably hit. The accepted floors:

  • 24 x 24px for desktop pointer interaction (WCAG 2.2 minimum).
  • 44 x 44px for touch targets (Apple HIG, WCAG enhanced).

These are the hit area, not the visual size. A 16px icon button can have a 44px hit area if its padding is generous:

.icon-button {
  padding: 14px;
  /* or expand with a pseudo-element: */
}
.icon-button::after {
  content: "";
  position: absolute;
  inset: -14px;
}
Hit target visualizer
16pxtoo small
20pxtoo small
18pxtoo small
14pxtoo small
32pxtoo small
24pxtoo small
6 of 6 controls fail the 44px minimum for touch input

The pseudo-element trick is especially useful when the icon must remain visually small but live near other clickables—the invisible padding catches the press without overlapping neighbours.

Adjacent targets need at least 8px between hit areas to prevent accidental presses. Toolbars with icon buttons jammed together regularly mis-fire—the user means bold, hits italic, undoes, retries.

Hover gates

Touch devices don't have a hover state. When a user taps a button on a phone, the browser briefly fires hover events alongside the tap—which means hover styles flicker on for a frame before the action fires. The result is jarring, especially on slower devices.

The fix is one CSS media query—gate hover styles behind @media (hover: hover):

button {
  background: var(--rest);
}

@media (hover: hover) and (pointer: fine) {
  button:hover {
    background: var(--hover);
  }
}

(hover: hover) matches devices with a primary input that can hover. (pointer: fine) filters for precise pointing. Combined, they exclude phones and tablets cleanly.

What should still apply on touch:

  • :active for press feedback—briefly fires on tap, gives the button a moment of "I'm being pressed."
  • :focus-visible for keyboard focus—even on touch devices, an external keyboard can be connected.
  • Transitions triggered by state changes (icon swaps, checkbox ticks)—these aren't hover and should still happen on touch.

touch-action for gestures

When building custom gesture interactions—swipe-to-dismiss, pull-to-refresh, horizontal scrolling—the browser's default touch behaviours compete. touch-action tells the browser which gestures you're handling:

.swipe-panel {
  touch-action: pan-y;  /* browser handles vertical scroll; you handle horizontal */
}

Without this, a horizontal swipe on a vertically-scrolling page triggers both your gesture handler and the browser's scroll, producing a jittery mess.

Common values:

  • pan-y — allow vertical scroll, prevent horizontal browser gestures.
  • pan-x — allow horizontal scroll, prevent vertical browser gestures.
  • none — you handle everything (use sparingly—breaks default scrolling).
  • manipulation — allow scroll and pinch-zoom, but disable double-tap-to-zoom (removes the 300ms tap delay on older browsers).

Common mistakes

  • Modal opens, focus stays on the trigger button behind it. Tab navigates the page underneath, not the modal.
  • Modal closes, focus snaps to <body>. The user is lost.
  • Tiny close buttons in toasts and modals—especially on mobile. People miss them and dismiss the wrong thing.
  • Hover styles without the media query. Phone tap produces a flash of hover state before the click fires.
  • Removing -webkit-tap-highlight-color and providing nothing. Touches feel unresponsive. If you suppress the highlight, ensure :active provides feedback.
  • Nested clickables. A row containing a button: clicking the button activates the row too. Use event.stopPropagation() or restructure so they're not nested.