CSS overscroll-behavior: contain (No JavaScript)

Preventing scroll chaining without JavaScript

When a scrollable element inside a modal reached its end, scroll would chain to the page behind it. The fix was a JS wheel event listener calling e.preventDefault(). overscroll-behavior: contain stops that natively.

Old way 8 lines
.modal-content   {  overflow-y: auto;}// JS: prevent scroll chaining on wheel and touch modal.addEventListener('wheel', (e) => {   e.preventDefault(); }, { passive: false }); // also needed for touch: touchmove listener
4 lines
.modal-content   {  overflow-y: auto;  overscroll-behavior: contain;}
Widely available Since 2022 96% global usage

This feature is well established and works across many devices and browser versions. It has been available across browsers since 2022.

Safe to use without fallbacks.

63+
59+
16+
18+
scroll inside the box — the page does not scroll
Scrollable panel (overscroll-behavior: contain)
Row 1 — scroll past the end!
Row 2 — scroll past the end!
Row 3 — scroll past the end!
Row 4 — scroll past the end!
Row 5 — scroll past the end!
Row 6 — scroll past the end!
Row 7 — scroll past the end!
Row 8 — scroll past the end!
Row 9 — scroll past the end!
Row 10 — scroll past the end!
Row 11 — scroll past the end!
Row 12 — scroll past the end!
Scroll ends at the panel boundary — no JS needed

No JavaScript

No wheel or touchmove event listener, no preventDefault, no passive flag concerns.

Works on touch too

Handles touch scroll chaining on mobile without the complexity of non-passive event listeners.

Pull-to-refresh too

overscroll-behavior: none also prevents pull-to-refresh on Chrome Android for full-screen app-like layouts.

Lines Saved
8 → 4
No wheel event handler
Old Approach
e.preventDefault()
wheel and touchmove event listeners
Modern Approach
overscroll-behavior
CSS scroll containment

How it works

Scrollable panels inside modals or dropdowns would chain scroll to the page once the panel reached its end. Stopping this required non-passive wheel and touchmove event listeners calling e.preventDefault(), which also blocked the browser's scroll optimizations and required separate handling for touch.

overscroll-behavior: contain stops scroll propagation at the element's boundary. The inner element scrolls normally, but the chain ends there. overscroll-behavior: none additionally prevents the bounce effect and pull-to-refresh on supported platforms.

ESC