Layout Beginner

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.

Modern
4 lines
1.modal-content {
2  overflow-y: auto;
3  overscroll-behavior: contain;
4}
Old 8 lines
1.modal-content { overflow-y: auto; }
2// JS: prevent scroll chaining on wheel and touch
3modal.addEventListener('wheel', (e) => {
4  e.preventDefault();
5}, { passive: false });
6// also needed for touch: touchmove listener
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
Overscroll behavior
Widely available Since 2022 96% global usage
63+
59+
16+
18+

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

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.

New CSS drops every month.

Get one old → modern comparison in your inbox every week.

ESC