Controlling browser gestures deliberately instead of fighting them
touch-action decides which built-in gestures like pinch-zoom or scroll the browser handles for an element, and which instead get passed on to your own JavaScript. pointer-events answers a completely different question: whether an element reacts to pointer input at all. Keeping the two apart lets you build swipe carousels and touch widgets that do not fight the browser over every gesture.
Table of Contents
- 1. Two properties, two different questions
- 2. touch-action: an overview of the available values
- 3. Why touch-action improves perceived performance
- 4. pointer-events: removing elements from hit-testing
- 5. Practical recipe: a swipe carousel without fighting the browser
- 6. Drawing surfaces and gesture widgets with full control
- 7. Combining both properties in complex widgets
- 8. Common mistakes and how to spot them
- 9. A checklist for touch widgets in practice
- 10. Summary
- 11. FAQ
1. Two properties, two different questions
touch-action answers the question of which built-in touch gestures the browser handles for an element itself, such as vertical scrolling, horizontal scrolling or pinch-zoom, and which of those instead get passed on to your own JavaScript as plain pointer events. pointer-events answers an entirely different question: whether an element is even eligible as a target for pointer input, regardless of whether that input comes from a mouse, pen or finger.
The two are often confused because both relate to pointer input, yet they act at completely different points in the processing chain: touch-action takes effect before an event ever reaches JavaScript, because it tells the browser upfront whether it may handle a gesture itself. pointer-events takes effect during event delivery itself, deciding whether an element counts as a hit during hit-testing.
2. touch-action: an overview of the available values
The default value auto leaves every built-in gesture to the browser for that element, while none suppresses all browser-side gestures and hands full control to JavaScript. In between sit finer-grained values like pan-x, pan-y and pinch-zoom, each leaving exactly one axis or gesture to the browser while freeing up all others for custom code.
This fine-grained distinction is essential for performant touch widgets: a horizontal carousel typically sets touch-action: pan-y, so vertical page scrolling stays with the browser while horizontal swipe gestures go to the carousel's own script. Without this level of control, every gesture would need to be rebuilt from scratch, including momentum scrolling, which noticeably feels choppier than the native browser implementation.
/* Horizontal carousel: vertical scrolling stays with the browser */
.carousel-track {
touch-action: pan-y;
overflow-x: hidden;
display: flex;
}
/* Drawing canvas: hand full control to custom JS */
.drawing-canvas {
touch-action: none;
}
3. Why touch-action improves perceived performance
Without an explicit touch-action declaration, the browser must first wait, on every touch interaction, to see whether a registered touchmove handler cancels the gesture via preventDefault(), before it starts the actual scrolling. That waiting period, often called scroll delay, can noticeably feel like stutter on weaker devices, even when the JavaScript code itself never actually intervenes.
An explicit touch-action declaration tells the browser, before the very first touch event, which gestures it may safely handle itself without waiting for a possible JavaScript intervention. The result is noticeably more direct, delay-free scroll and zoom behavior, especially on mobile devices with limited processing power.
4. pointer-events: removing elements from hit-testing
pointer-events: none removes an element entirely from the browser's hit-testing, so clicks, taps and hover events pass right through it to whatever lies underneath. This is especially useful for purely decorative overlays, such as a gradient over an image or an icon sitting visually on top of a clickable button, whose clickability should stay undisturbed.
It is important that pointer-events: none has nothing to do with touch gestures like scroll or zoom; it purely determines whether the element itself qualifies as a target for a click, tap or hover. An element with pointer-events: none can still participate in an underlying scroll gesture, because that is governed by touch-action, not by pointer-events.
/* Decorative overlay, clicks pass through to the button underneath */
.card {
position: relative;
}
.card__gradient-overlay {
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(to top, rgba(0,0,0,0.4), transparent);
}
5. Practical recipe: a swipe carousel without fighting the browser
A robust swipe carousel combines touch-action: pan-y on the carousel container with custom JavaScript that only evaluates horizontal swipe movement. Because the browser still handles vertical scrolling itself, the page stays smoothly scrollable even when a finger accidentally moves slightly diagonally across the carousel, instead of the script needing to interpret both axes at once.
For navigation arrows sitting on top of the carousel that should not block the swipe gesture, a combination of touch-action: manipulation, which suppresses double-tap zoom on the arrow button, and a clean z-index helps, keeping the button clickable above the carousel area without disrupting the underlying swipe gestures.
.carousel {
position: relative;
overflow: hidden;
}
.carousel-track {
touch-action: pan-y;
display: flex;
will-change: transform;
}
.carousel-arrow {
position: absolute;
top: 50%;
touch-action: manipulation;
z-index: 2;
}
6. Drawing surfaces and gesture widgets with full control
For drawing canvases, signature fields, or custom gesture recognition, such as a map application with its own pinch-to-zoom implementation, touch-action: none is the right choice, because every movement there must be fully interpreted by custom code. In this case the browser must not handle a single gesture itself, neither scroll nor pinch-zoom, otherwise two interpretations of the same finger movement end up competing.
A common mistake is accidentally setting touch-action: none on a parent element that should actually remain scrollable, such as the entire page content instead of just the embedded drawing canvas. That leaves users unable to scroll the page itself as soon as they start touching anywhere in the affected area, a classic usability break on mobile devices.
7. Combining both properties in complex widgets
In more complex components, like an image gallery with pinch-zoom and overlaid controls, both properties are often used together: touch-action controls which gestures on the image surface itself go to custom code, while pointer-events: none on purely decorative intermediate layers ensures controls like a close button remain clickable even though an overlay visually sits above them.
Keeping the two responsibilities cleanly separated also makes debugging considerably easier: if an element does not react to clicks at all, the cause is almost always pointer-events. If a gesture feels delayed, jittery, or misinterpreted, the cause is almost always a missing or incorrect touch-action declaration.
8. Common mistakes and how to spot them
A widespread mistake is using pointer-events: none to try to block touch gestures, even though it only affects click and hover events; scroll and zoom gestures keep running completely unaffected. Anyone who actually wants to control gestures must reach for touch-action instead, no matter how similar the two property names sound.
Another mistake is treating touch-action: manipulation as a universal cure-all value everywhere. The value does suppress double-tap zoom and other delays, but it also disables pinch-zoom entirely, which unnecessarily limits usability on an element that should actually remain zoomable.
9. A checklist for touch widgets in practice
Before shipping, it is worth checking three questions: which gestures should the browser handle itself and which should go to custom code, which elements need to stay clickable despite a visual overlay on top, and is touch-action: none really scoped to the smallest possible, actually necessary area.
Anyone who answers these three questions individually for every touch widget avoids the most common symptoms of misconfigured touch interaction: blocked page scrolling, jittery swipe gestures, and controls that do not respond despite being visibly present.
| Property | Controls | Affects scroll/zoom | Affects click/hover |
|---|---|---|---|
touch-action: auto |
Default, browser handles every gesture | Yes | No |
touch-action: pan-y |
Leaves only vertical scrolling to the browser | Yes | No |
touch-action: none |
Hands every gesture to custom code | Yes | No |
pointer-events: none |
Removes an element from hit-testing | No | Yes |
pointer-events: auto |
Default, element reacts normally | No | Yes |
Mironsoft
Modern CSS, layout architecture and rendering performance
CSS that stays maintainable instead of breaking with every change?
We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.
CSS Audit
Systematically uncovering specificity issues, cascade conflicts and unused selectors.
Architecture Refactoring
Introducing cascade layers, custom properties and design tokens cleanly.
Performance Tuning
Fixing layout thrashing, expensive selectors and rendering bottlenecks.
10. Summary
touch-action and pointer-events: The Essentials at a Glance
touch-action
Controls which built-in gestures like scroll or pinch-zoom the browser handles itself and which go to custom code.
pointer-events
Controls whether an element can be a target for click, tap and hover events at all, independent of gestures.
Swipe carousels
touch-action: pan-y on the track leaves vertical scrolling to the browser while horizontal swipes go to custom JS.
Drawing canvases
touch-action: none hands every finger movement fully to custom code, but should be scoped as tightly as possible.