The right fix for double-tap zoom is one CSS line, not a viewport lock
Marcus Osei
There is a category of "accessibility fix" that fixes nothing and locks people out. Disabling zoom on the whole page to stop one button from misbehaving is the classic example. If a user rapid-taps a control on your radio player, your podcast list, your carousel of station cards, and the page yanks itself larger under their thumb, the answer is not to take zoom away from everyone who needs it. The answer is to tell that one button to stop treating a fast second tap as a gesture.
Andy Bell ran into this on a web radio player he was trying on his phone. Flicking rapidly through stations to find one on air, iOS Safari read the fast successive taps as its double-tap-to-zoom gesture, and the page zoomed in and out under his fingers. The write-up is short. The fix is shorter.
The reflex fix costs more than it saves
You have seen the workaround before. Something in the UI misbehaves under fast taps, and someone reaches for the viewport meta tag:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
That kills every zoom gesture on the whole page. Not just the double-tap on the button. Pinch, too. A user with low vision cannot enlarge the text. A user of a magnifier extension has nothing left to work with. Adding maximum-scale=1.0, user-scalable=no to the viewport is called out in Andy's write-up as a WCAG violation, and it has been called out as one for as long as mobile Safari has shipped. The bug is fixed; the page is broken for the people who most need it to work.
If your accessibility patch takes an ability away from every visitor to stop one gesture on one control, you have shipped a regression.
Scope the opt-out to the control
The tool Andy reaches for is touch-action, applied directly to the button:
button {
touch-action: manipulation;
}
That is the whole change. Per MDN, touch-action: manipulation is an alias for pan-x pan-y pinch-zoom. The browser keeps single-finger panning. It keeps two-finger pinch to zoom. What it drops is the browser's own set of extra gestures, most notably double-tap to zoom. A rapid second tap on a control that carries manipulation fires as a second click, not as a zoom.
There is a small side benefit. touch-action: manipulation also removes the click-event delay that browsers historically inserted after a tap so they could disambiguate a first tap from an incoming double tap. On a control that has opted out of the second-tap gesture, there is nothing to wait for.
Two things to notice about this fix, both of them accessibility properties in their own right:
- It only affects the element you put it on. The rest of the page, headings and body copy and the whole reading surface, still zooms normally.
- It targets the browser's page-level zoom gestures on that one element, not the ability to zoom itself. Pinch stays.
That is what an opt-out should look like: applied to the control that has a reason to opt out, and no wider.
For your next PR
- If a control misfires on fast taps, put
touch-action: manipulationon that control, not on the page. - Do not touch the viewport meta to fix a per-button problem.
maximum-scale=1.0anduser-scalable=noare the anti-pattern here, not the escape hatch. - Leave pinch zoom alone. It is a feature for the people who need it to read your interface, not a defect for you to close.
- If a viewport lock is already sitting in a shared template somewhere, this is a good week to open the PR that pulls it.
Source: Piccalilli (piccalil.li)