Part of the Responsive Layouts for Automated Geo-Dashboards guide.
Operative rule: the hit target, not the icon, must be at least 44 by 44 pixels — enlarging the drawn control is how a phone map ends up covered in chrome.
Three Things Change on a Phone
Shrinking a desktop control layout produces a map that technically fits and is unpleasant to use. Three properties change at once, and each needs a separate decision: how big a target must be, where a thumb can comfortably reach, and how many controls are worth keeping at all.
The reachability point is the one most often missed. Desktop convention puts zoom controls at the top right, which on a phone held one-handed is the corner furthest from the thumb — and it is also where a reader’s hand covers the map while reaching.
Production-Ready Implementation
/* Touch targets: the hit area is the requirement, the icon is not. */
.map-control {
--control-size: 44px;
min-inline-size: var(--control-size);
min-block-size: var(--control-size);
display: grid;
place-items: center; /* icon stays small, target does not */
border-radius: 10px;
}
.map-control + .map-control { margin-block-start: 8px; } /* no mis-taps */
/* Desktop: conventional top-right stack. */
.map-controls {
position: absolute;
inset-block-start: 12px;
inset-inline-end: 12px;
display: flex;
flex-direction: column;
}
/* Small screens: move into thumb reach and clear the browser gesture band. */
@media (max-width: 640px) {
.map-controls {
inset-block-start: auto;
inset-block-end: calc(16px + env(safe-area-inset-bottom, 0px));
inset-inline-end: 12px;
}
.map-control { --control-size: 48px; } /* a little larger under a thumb */
.map-control--secondary { display: none; } /* prune, do not shrink */
}
/* Pointer-coarse devices get the larger target regardless of width. */
@media (pointer: coarse) {
.map-control { --control-size: 48px; }
}
// One-finger drag pans the page; two fingers pan the map. Prevents the
// "I cannot scroll past the map" complaint without disabling interaction.
export function cooperativeGestures(map, container, hint) {
if (!window.matchMedia("(pointer: coarse)").matches) return;
map.dragPan.disable();
container.addEventListener("touchstart", (event) => {
if (event.touches.length >= 2) {
map.dragPan.enable();
hint.hidden = true;
} else {
map.dragPan.disable();
hint.hidden = false; // "use two fingers to move the map"
setTimeout(() => { hint.hidden = true; }, 1600);
}
}, { passive: true });
container.addEventListener("touchend", (event) => {
if (event.touches.length < 2) map.dragPan.disable();
}, { passive: true });
}
env(safe-area-inset-bottom) is what keeps controls clear of the home indicator and gesture band on modern phones. Without it a control sits exactly where the operating system expects a swipe, and every third tap navigates away from the page instead.
Prune Rather Than Shrink
Gestures That Do Not Fight the Page
A map that captures one-finger drags inside a scrolling article traps the reader: they swipe to scroll, the map pans, and the page does not move. Cooperative gestures fix it by requiring two fingers to move the map, with a brief hint the first time a single finger is used. The same applies to pinch-zoom versus page zoom, and to any drawing tool that takes over the surface.
Verification Steps
- Measure every control’s hit area in the browser’s device toolbar and confirm 44 pixels or more.
- Confirm 8 pixels of separation between adjacent targets by tapping each on a real device.
- Scroll a page containing the map using one finger over the map and confirm the page moves.
- Confirm controls sit above the safe-area inset on a phone with gesture navigation.
- Open the layer sheet and confirm its rows are full-size, not shrunken desktop checkboxes.
Common Errors & Fixes
Taps land on the wrong control
Targets are adjacent with no gap. Add spacing between them; a gap is cheaper than a larger control.
Controls trigger browser navigation
They sit in the gesture band at the very bottom edge. Add env(safe-area-inset-bottom) to the offset.
The map is unusable inside a long article
One-finger drag is panning the map. Enable cooperative gestures on coarse pointers.
Controls look right in the emulator and are too small on a device
Emulator scaling hides the real size. Measure on hardware, or at minimum check the computed pixel dimensions rather than the rendered appearance.
The Bottom Sheet Pattern
Collapsing secondary controls behind one button only helps if what opens is designed for a phone rather than borrowed from the desktop panel. A bottom sheet — a panel that slides up from the lower edge, covering part of the map — is the pattern that fits, and it works because it puts its contents exactly where the thumb already is.
Three details make one usable. It should be dismissible by swiping down as well as by a close button, because that is the gesture readers try first. It should cover part of the map rather than all of it, so the reader keeps their context and can see the effect of a change without closing the sheet. And its rows should be full-height touch targets with the label as part of the target, not a small checkbox beside text that is not tappable.
Sizing is worth thinking about too. A sheet that opens to roughly half the viewport shows six or seven rows, which covers most layer lists without scrolling; anything longer should scroll inside the sheet rather than expanding it to full height, so the map stays partly visible. If the list is long enough that scrolling is routine, that is a signal to group the layers rather than to enlarge the sheet.
The same container solves the legend problem. On a wide screen a legend can sit permanently in a corner; on a phone it competes directly with the map for the small space available. Putting it in the same sheet — as a second section, or a second tab — gives it full-width rows where its labels are actually readable, at the cost of one tap.
Testing on Something Real
Emulator device modes are useful for layout and misleading for touch. They scale the viewport but not the input: a control that measures 44 pixels in the emulator is 44 CSS pixels on the device too, but the emulator’s mouse pointer hits it perfectly every time in a way a thumb does not.
The checks that only hardware gives you are whether adjacent controls can be hit reliably at speed, whether a reaching thumb obscures something important, and whether the browser’s own gesture areas interfere. All three are quick to assess and none are visible in a desktop browser. One five-minute pass on a real phone, once per significant layout change, catches more than any amount of emulator work.
Gotchas & Edge Cases
- A control that is 44 pixels tall but 24 wide fails the requirement; both dimensions matter, and icon-only buttons are where this is most often missed.
- Hover styles applied on a touch device stick after a tap in some browsers, leaving a control looking permanently active — scope them inside a fine-pointer media query.
- The on-screen keyboard shrinks the viewport without firing a window resize on every platform; observe the container so a search field opening does not leave the map sized for a taller screen.
- Landscape orientation on a phone leaves very little vertical room; a bottom sheet sized as a fraction of the viewport handles it, a fixed-height one does not.
- Disabling page zoom to stop pinch conflicts also removes a reader’s ability to enlarge text, and is an accessibility regression rather than a fix.
Related
- Responsive Layouts for Automated Geo-Dashboards — the parent guide covering breakpoints and resize handling
- Making Python-Generated Maps Responsive on Mobile — the interaction-parity work these controls sit inside
- Popups, Tooltips & Feature Interaction — sizing feature hit targets by the same rule
- Layer Management & Toggling — what goes inside the collapsed layer sheet