touch-input
Designing for the Thumb
Assume the pointer is coarse, imprecise, attached to a hand that covers part of the screen, and incapable of hovering. That single assumption decides most of what follows: hit areas grow past their visuals, hover reveals but never enables, feedback arrives on press rather than on release, and every gesture has to declare which axis it wants before the browser has to guess. Design the touch version as the real one and let the fine-pointer version be the variant — the reverse produces interfaces that are technically usable on a phone and miserable in practice. gestures owns everything that happens once the finger starts moving — velocity, springs, rubber-banding, mid-flight interruption; this skill stops at the moment of contact: size, placement, latency, and which axis wins. Keyboard order, focus and ARIA are a11y; whether the app reads as installed is native-feel.
Work in the project's existing responsive and styling system. If it has a hover: variant strategy, a size-* scale, or a touch-target utility, extend that rather than adding raw media queries beside it. Check whether an interaction library (Vaul, Embla, use-gesture, Radix) already owns touch-action and pointer capture on the component you are touching — two things setting touch-action on the same element is a bug that only reproduces on device. If the project targets desktop only today, add capability queries anyway; they cost nothing and a touchscreen laptop is already in the wild.
| Topic | Reference |
|---|---|
| A bug that only reproduces on an iPhone | Open references/platform-quirks.md when something works everywhere except iOS Safari — input zoom, video autoplay, viewport height, tap highlight, scroll locking. |
Core Principles
-
The hit area is
44×44; the visual can be anything. Apple's HIG sets44×44ptas the minimum comfortable target, and a 24px icon button with a 24px hit area is the single most common touch defect. Keep the icon at24pxand expand the target withmin-width: 44px; min-height: 44px, or a pseudo-element atinset: -10px. Exception: a target inside a run of inline text cannot be 44px tall without wrecking the line — WCAG 2.2 SC 2.5.8 puts the hard floor at24×24CSS px and demands clear spacing whenever you sit under 44. -
Adjacent targets need dead space between them. The finger's contact patch is far wider than the single point the browser reports, so two hit areas that touch will fire the wrong one at the edges. Leave at least
8pxof untargeted space between independent controls (house default), and check that expanded hit areas do not overlap — a delete whose invisible padding reaches under a confirm is a data-loss bug that no screenshot shows. Exception: segmented controls, paginators and calendar grids, where the options are adjacent by design and none is destructive. -
Gate hover on capability, never on width. Touch devices fire
:hoveron tap and leave the state stuck until the next tap elsewhere. Wrap every hover style in@media (hover: hover) and (pointer: fine)(Emil Kowalski). A viewport-width breakpoint is the wrong test: a touchscreen laptop is wide and coarse, and a phone in a desktop-mode browser is neither. Exception: a purely decorative cursor-follow effect may gate on(pointer: fine)alone, since it has no state to get stuck in.