
Mobile is two questions
On this page
I had a UI that opened little windows at pixel coordinates, desktop-style, and on a phone it fell apart. Windows opened off-screen. Actions hid behind hover and right-click, which a finger cannot do. On a phone it was not awkward, it was unusable.
My first instinct was the wrong one. Add a mobile breakpoint. One line in the sand at some width, desktop on one side, mobile on the other.
That instinct is what kept tablets broken.
One breakpoint is the wrong model#
A single width breakpoint bundles two unrelated things together and assumes they always agree. They do not.
“How much room do I have” is a question about the viewport. “Can this user hover and right-click” is a question about the input device. A phone answers small and no. A desktop answers large and yes. But a large tablet answers large and no, and that is exactly the case one breakpoint gets wrong every time.
Treat the tablet as a narrow desktop and it keeps hover-only menus a finger cannot reach. Treat it as a big phone and you throw away a perfectly good roomy layout. Either way it is the broken middle case, and it is broken because you asked one question when there were two.
Split the signal in two#
So the redesign uses two independent media queries that do not talk to each other.
A max-width query decides layout. Below the threshold, the whole thing swaps to a full-screen mobile shell instead of floating pixel-positioned windows. This is purely about space.
A pointer: coarse query decides affordances. When the pointer is coarse, meaning a finger, the UI grows its tap targets and swaps hover and right-click menus for long-press. This is purely about input.
@media (max-width: 720px) { /* layout: mobile shell */ }@media (pointer: coarse) { /* affordances: touch */ }Now the large tablet gets the answer it always deserved. It keeps the desktop layout, because it has the room, and it gains touch controls, because it has a finger. Two questions, two answers, no contradiction.
Mobile is two questions: how big is the viewport, and can the user hover and right-click. Model them separately and tablets stop being a broken middle case.
The prerequisite and the honest deferral#
None of this worked until I fixed a quieter bug first. The old code read the viewport width once, on load. A one-shot read never updates on resize or rotate, so the layout was frozen to whatever the first frame happened to be. I replaced it with a reactive media-query hook that actually re-evaluates when the match changes. Split signals are useless if you only sample them once.
And one thing I did not solve, so I will be honest about it. There is a drag gesture that, on touch, fights vertical scroll. Drag and scroll share an axis, and the browser cannot always tell which one the user meant. That needs a separate tap-only variant, and I deferred it rather than shipping a gesture that hijacks the page scroll.
When something is “not mobile friendly,” resist the single breakpoint. Ask the two real questions. Size decides layout. Input decides affordances. Wire them to separate queries and the tablet stops being a bug. Left unfixed, that broken middle is just every tablet user meeting menus a finger cannot reach and quietly deciding the app does not work.
And budget for touch gestures as their own piece of work. Drag and scroll live on the same axis, so any drag interaction needs a deliberate touch path, not a hope that it will just work.


