Chrome Touch Event Warnings in a WordPress Theme: Passive Listeners Explained

Quick Summary
- Chrome's Added non-passive event listener warning is a scroll-performance hint: add { passive: true } to touchstart and touchmove listeners that never call preventDefault().
- A handler that must call preventDefault() needs { passive: false }, or better a CSS touch-action value such as pan-y on the element.
- jQuery's .on() cannot add passive listeners, so the warning often comes from jQuery or an old slider; update or replace it, or patch jQuery.event.special and test swipes on a phone.
Chrome logs [Violation] Added non-passive event listener to a scroll-blocking 'touchstart' event when a script adds a touchstart (or touchmove) listener without saying it’s passive. It’s a performance hint, not an error: nothing on the page is broken. To fix the non-passive event listener warning in your own code, add { passive: true } to addEventListener(), unless the handler calls preventDefault(). In a WordPress theme it usually comes from jQuery or a jQuery slider, because jQuery’s .on() can’t add passive listeners; then update or replace the library, or use the jQuery workaround below and test it.
All results in this post come from headless Chrome 153 with jQuery 3.7.1, the version WordPress 7.1 bundles.
What the warning means
When a finger touches the screen, the browser wants to start scrolling straight away. But any touchstart or touchmove listener could call preventDefault() to stop the scroll, so the browser has to run those listeners first and wait for them. If the main thread is busy with other JavaScript, the scroll starts late and feels sticky.
A passive listener is a promise: “I won’t call preventDefault()“. With that promise the browser can scroll at once and run the listener alongside. The full message says so:
[Violation] Added non-passive event listener to a scroll-blocking 'touchstart' event. Consider marking event handler as 'passive' to make the page more responsive.
A few things about it:
- It’s logged when the listener is added, not when it slows anything down. A cheap handler on a small element triggers it just the same.
- It’s logged at the Verbose level, so you only see it in DevTools when Verbose is ticked in the console’s log levels.
- When a touch was actually delayed, Chrome adds a second message, for example
Handling of 'touchstart' input event was delayed for 3 ms due to main thread being busy. That one tells you it had a real cost. - Listeners on
window,documentordocument.bodydon’t trigger it. Chrome and Firefox already treattouchstart,touchmove,wheelandmousewheellisteners there as passive by default (MDN: every browser except Safari). In my test, a jQuery delegated handler,jQuery( document ).on( 'touchstart', '#box', ... ), logged nothing for that reason.
When it matters
It matters on phones and tablets, on pages where a non-passive touch listener covers a large area (a full-width slider, a header, a wrapper around the content) and the page runs a lot of JavaScript. That’s when scrolling feels delayed. On desktop with a mouse, touch listeners don’t run at all.
If the only source is one small element with a tiny handler, the warning costs you almost nothing. Still, it’s usually a one-word fix in your own code, so fix it there.
How to fix it in your own code
If the handler only reads the touch (tracks position, starts an animation, toggles a class), mark it passive:
const slider = document.querySelector( '.my-slider' );
let startX = 0;
slider.addEventListener( 'touchstart', function ( event ) {
startX = event.touches[0].clientX;
}, { passive: true } );The warning is gone and the handler still runs.
If the handler has to call preventDefault(), for example a custom swipe (moveSlide() stands for your own code) that must stop the page scrolling while it moves, it can’t be passive. Say so explicitly with { passive: false }. Chrome doesn’t log the violation for an explicit passive: false, and preventDefault() works:
slider.addEventListener( 'touchmove', function ( event ) {
event.preventDefault(); // Stops the page scrolling while the slide moves.
moveSlide( event.touches[0].clientX - startX );
}, { passive: false } );Better still, tell the browser up front with CSS which gestures it should handle itself, using touch-action. For a horizontal slider, touch-action: pan-y lets the browser scroll the page vertically while your code handles horizontal swipes, often without any preventDefault():
.my-slider {
touch-action: pan-y;
}Don’t mark a listener passive if it calls preventDefault(). Chrome then ignores the call and logs Unable to preventDefault inside passive event listener invocation., and the page scrolls anyway.
The other message: “Unable to preventDefault inside passive event listener”
This post first covered a related error, reported under these names:
1. [Chrome Dev Tools – Mobile] Unable to preventDefault inside passive event listener due to target being treated as passive
2. OwlCarousel2 Console warning when using touch events
It’s the opposite case. A script adds a touch listener to document (or window or body) without a passive option, Chrome treats it as passive by default, and the script then calls preventDefault(). Chrome logs [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive and ignores the call. The fix in code is to add { passive: false } to that listener; in my test that made preventDefault() work and the message went away.
My original fix was CSS in the theme’s main stylesheet:
body {
touch-action: manipulation;
}or, for Owl Carousel:
.owl-carousel {
touch-action: none;
}In Chrome 153 both still stop the message. But they don’t make preventDefault() work again: in my test the call was still ignored, the message just wasn’t printed. Know what each one does:
touch-action: manipulationkeeps scrolling and pinch zoom and only turns off double-tap zoom. It’s harmless onbody.touch-action: noneturns off all browser panning and zooming on that element. On a phone, a swipe that starts on the carousel won’t scroll the page up or down. For a horizontal carousel,touch-action: pan-yis usually the better value: vertical scrolling still works, and in my test it also stopped the message.
Why the warning often comes from jQuery or a slider
jQuery’s .on() has no way to add a passive listener. In jQuery 3.7.1 it calls elem.addEventListener( type, eventHandle ) with no options, so every .on( 'touchstart', ... ) or .on( 'touchmove', ... ) on an element logs the violation. The feature request for passive listeners has been open since 2016 and is now on the jQuery 5.0 milestone.
So a slider or menu plugin built on jQuery will trigger the warning even when your own code is clean. To find the source, open DevTools, select the element, and look at the Event Listeners tab: it lists each touchstart listener, whether it’s passive, and the script it came from.
What to do when a library adds the listener
1. Update the library. Check its changelog for passive listeners or touch-action support, and test whether the warning is gone after the update.
2. Replace it if it’s abandoned. Owl Carousel 2 is one example: its GitHub repository now marks it as deprecated and tells users to switch to another slider. A maintained slider that uses passive listeners or touch-action fixes this at the source.
3. Make jQuery’s touch listeners passive. jQuery lets you take over how a listener is added through jQuery.event.special. This version of a snippet that is widely shared online makes touchstart and touchmove listeners added with .on() passive, unless you bind them with a .nonpassive namespace:
( function ( $ ) {
[ 'touchstart', 'touchmove' ].forEach( function ( type ) {
$.event.special[ type ] = {
setup: function ( data, namespaces, handler ) {
this.addEventListener( type, handler, {
passive: namespaces.indexOf( 'nonpassive' ) === -1
} );
}
};
} );
} )( jQuery );What I saw with jQuery 3.7.1 in Chrome 153:
$( el ).on( 'touchstart touchmove', fn ): no violation, and the handler still ran on a touch..off()still removed the handlers.- A handler that calls
preventDefault()broke: Chrome loggedUnable to preventDefault inside passive event listener invocation.and the default wasn’t prevented. Bound as.on( 'touchmove.nonpassive', fn ), it worked again. setupruns only for the first handler of that event on an element, so the first binding decides for all of them. When I bound a plain handler first and a.nonpassiveone second on the same element, the second one’spreventDefault()was ignored.
That third point is the catch. If a slider calls preventDefault() in its touch handlers, this snippet silently turns that off, and swiping the slider can scroll the page at the same time. The warning is only a hint, so don’t trade a working slider for a clean console: add the snippet, then test every swipe, menu and lightbox on a real phone.
The snippet has to run after jQuery loads and before the library binds its handlers. In WordPress, the reliable place is an inline script attached to jquery-core. Add this to your (child) theme’s functions.php:
function mytheme_passive_touch() {
$js = <<<'JS'
( function ( $ ) {
[ 'touchstart', 'touchmove' ].forEach( function ( type ) {
$.event.special[ type ] = {
setup: function ( data, namespaces, handler ) {
this.addEventListener( type, handler, {
passive: namespaces.indexOf( 'nonpassive' ) === -1
} );
}
};
} );
} )( jQuery );
JS;
wp_add_inline_script( 'jquery-core', $js, 'after' );
}
add_action( 'wp_enqueue_scripts', 'mytheme_passive_touch' );WordPress then prints the snippet in a tag right after jquery.min.js and before jQuery Migrate and any script that depends on jQuery. It only loads on pages where something already enqueues jQuery.
Related articles
- Fix TypeError: $ is not a function when using jQuery in WordPress: why the snippet wraps its code in
( function ( $ ) { ... } )( jQuery ) - Add a class on horizontal scroll with jQuery: a jQuery scroll handler on a sideways-scrolling menu