Your app was tested with a thumb. Then a user opens it on a Chromebook, docks a foldable to a keyboard, or scribbles in a notes screen with a stylus, and a dozen assumptions fail quietly. Nothing crashes at first. The right-click does nothing, Tab skips the primary button, and holding Ctrl+S writes the file nine times.
This post walks through how input actually flows in Jetpack Compose, the idiomatic Kotlin for handling each device, and the failure modes that tend to reach production because nobody had a mouse on their desk during QA.
The mental model: two lanes, not one
Touch handling trains us to think in “taps.” Compose does not. It routes pointer events and key events through different machinery, and mixing them up is the root of most bugs below.
Pointer events (touch, mouse, trackpad, stylus) are hit-tested against the nodes under the pointer, then delivered in passes: initial (parent to child, so a parent can intercept), main (child to parent, where most code lives), and final.…