Interaction Details
10 minInteraction 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>.
Subscribe for updates
Tab through the controls to see focus flow.
Click into the modal and start tabbing.
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;
}
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:
:activefor press feedback—briefly fires on tap, gives the button a moment of "I'm being pressed.":focus-visiblefor 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-colorand providing nothing. Touches feel unresponsive. If you suppress the highlight, ensure:activeprovides 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.