What Are Passive Event Listeners? Complete Guide (2026)

Saar Twito6 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

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, and wheel passive 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

EventDefault at document/windowDefault on elementsRecommended
touchstartPassive (Chrome 56+)Non-passive{ passive: true } unless cancelling
touchmovePassive (Chrome 56+)Non-passive{ passive: true } unless cancelling
wheelPassive (Chrome 73+)Non-passive{ passive: true } for analytics or animation triggers
mousewheel (legacy)PassiveNon-passiveUse wheel instead
scrollAlways passive (cannot be cancelled)Always passiveFlag is unnecessary but harmless
touchend / touchcancelNon-passiveNon-passivePassive flag has no effect (cannot cancel scroll)

How to Make a Listener Passive: Step by Step

  1. Open Chrome DevTools and check the Console for "Added non-passive event listener" warnings.
  2. Search the codebase for addEventListener('touchstart', 'touchmove', and 'wheel'.
  3. Add { passive: true } as the third argument unless the listener calls preventDefault().
  4. For listeners that conditionally cancel, split into two listeners: one passive for tracking, one non-passive only on the elements that need to cancel.
  5. Re-run an automated audit and confirm uses-passive-event-listeners passes.

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 touchmove listener with preventDefault() just to disable pull-to-refresh — use overscroll-behavior: contain in 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 wheel listener at the window level for analytics, blocking every scroll on the page.
  • Forgetting that jQuery's .on() attaches non-passive listeners by default — use native addEventListener for scroll-related events.

How to Test Passive Listener Coverage

  1. Open DevTools Console and look for "Added non-passive event listener to a scroll-blocking event" warnings.
  2. Run an automated audit and check the uses-passive-event-listeners result.
  3. Use the Performance panel: record a scroll, look for "Input delay" markers in the Interactions track.
  4. On mobile, throttle CPU to 4x and scroll — janky scroll usually means a non-passive handler.
  5. 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.