What Are Passive Event Listeners? Complete Guide (2026)
What Are Passive Event Listeners?
A passive event listener is an addEventListener registered with the option { passive: true }, which promises the browser the listener will not call preventDefault(). With that promise, the browser can start scrolling immediately instead of waiting for the listener to finish. Passive listeners matter most for touchstart, touchmove, wheel, and mousewheelevents — the four events that can cancel scrolling. The "Use passive listeners to improve scrolling performance" audit fails when any of these events are registered without the passive flag on a scrollable target.
Key Facts (TL;DR)
- Passive listeners are defined in DOM Living Standard, supported in all browsers since 2016.
- Chrome made
touchstart,touchmove, andwheelpassive by default at the document and window level since Chrome 56 (2017). - Listeners attached to specific elements still default to non-passive.
- Calling
preventDefault()inside a passive listener throws a console warning and is silently ignored. - Non-passive scroll-related listeners can delay scrolling by 100ms or more on mid-tier mobile.
- Audit ID:
uses-passive-event-listeners.
Event Defaults: Reference Table
| Event | Default at document/window | Default on elements | Recommended |
|---|---|---|---|
touchstart | Passive (Chrome 56+) | Non-passive | { passive: true } unless cancelling |
touchmove | Passive (Chrome 56+) | Non-passive | { passive: true } unless cancelling |
wheel | Passive (Chrome 73+) | Non-passive | { passive: true } for analytics or animation triggers |
mousewheel (legacy) | Passive | Non-passive | Use wheel instead |
scroll | Always passive (cannot be cancelled) | Always passive | Flag is unnecessary but harmless |
touchend / touchcancel | Non-passive | Non-passive | Passive flag has no effect (cannot cancel scroll) |
How to Make a Listener Passive: Step by Step
- Open Chrome DevTools and check the Console for "Added non-passive event listener" warnings.
- Search the codebase for
addEventListener('touchstart','touchmove', and'wheel'. - Add
{ passive: true }as the third argument unless the listener callspreventDefault(). - For listeners that conditionally cancel, split into two listeners: one passive for tracking, one non-passive only on the elements that need to cancel.
- Re-run an automated audit and confirm
uses-passive-event-listenerspasses.
Bad: blocks scrolling
// The browser must wait for this handler before scrolling
window.addEventListener('touchstart', (e) => {
trackScrollDepth();
});
window.addEventListener('wheel', (e) => {
updateParallax();
});Good: passive listener, no scroll delay
window.addEventListener('touchstart', (e) => {
trackScrollDepth();
}, { passive: true });
window.addEventListener('wheel', (e) => {
updateParallax();
}, { passive: true });When you genuinely need preventDefault
// Carousel that swallows horizontal swipes only on its own track
const track = document.querySelector('.carousel-track');
track.addEventListener('touchmove', (e) => {
if (Math.abs(e.touches[0].clientX - startX) > 10) {
e.preventDefault(); // requires non-passive
}
}, { passive: false });Common Mistakes
- Adding a global
touchmovelistener withpreventDefault()just to disable pull-to-refresh — useoverscroll-behavior: containin CSS instead. - Using
{ passive: false }on a listener that never actually cancels — the flag should match what the code does. - Calling
preventDefault()inside a passive listener and assuming it worked. It is silently ignored. - Attaching a non-passive
wheellistener at the window level for analytics, blocking every scroll on the page. - Forgetting that jQuery's
.on()attaches non-passive listeners by default — use nativeaddEventListenerfor scroll-related events.
How to Test Passive Listener Coverage
- Open DevTools Console and look for "Added non-passive event listener to a scroll-blocking event" warnings.
- Run an automated audit and check the
uses-passive-event-listenersresult. - Use the Performance panel: record a scroll, look for "Input delay" markers in the Interactions track.
- On mobile, throttle CPU to 4x and scroll — janky scroll usually means a non-passive handler.
- Use the Event Listeners pane in DevTools (Elements tab) to inspect which listeners are passive.
FAQ
What does passive: true actually do?
It tells the browser the listener will not call preventDefault(), so the browser can scroll without waiting for the listener to run. The listener still fires, just on a separate track.
Which events should be passive?
touchstart, touchmove, wheel, and mousewheel. These are the only events that can cancel scrolling, so they are the only ones the flag affects.
Did Chrome already make these passive by default?
Only at the document and window level. Listeners attached to specific elements still default to non-passive, which is why the audit still finds violations.
What happens if I call preventDefault in a passive listener?
The browser ignores the call and logs a warning to the console. The default action — scrolling — proceeds normally.
How do I disable pull-to-refresh without a non-passive listener?
Use the CSS overscroll-behavior: contain or overscroll-behavior-y: none. It is faster and does not require any JavaScript.
Do passive listeners affect INP?
Yes indirectly. A non-passive scroll-blocking listener delays the visual response to user input, which counts toward INP. Making the listener passive removes that delay.
Is feature detection still needed?
No. Every browser still in use supports the options object since 2016. You can safely write { passive: true } without a fallback.
Does this affect AI search engines like ChatGPT and Perplexity?
Indirectly, yes. AI search engines preferentially cite pages that already rank well, and scroll jank from non-passive listeners hurts INP — a Core Web Vital that feeds Page Experience signals. Better UX scores raise rankings, which raises the odds that AI systems pick your page when answering a query.
Conclusion
Add { passive: true } to every touchstart, touchmove, and wheel listener that does not need to cancel scrolling. Reach for CSS overscroll-behavior instead of non-passive listeners when you only want to stop pull-to-refresh. The fix is a few characters per call site and removes one of the most common sources of mobile scroll jank. Run a Greadme deep scan to find non-passive scroll-blocking listeners across your site.
