overscroll-behavior
overscroll-behavior controls what happens when a user keeps scrolling past the boundary of a scrollable area — the moment they hit the top or bottom of a box and keep dragging, swiping, or turning the wheel. By default, the browser lets that extra scroll "chain" into the next scrollable ancestor, usually the page itself. That default is the cause of one of the most common, genuinely annoying scroll bugs on the web.The problem: scroll chaining
Picture a modal dialog with its own internally scrolling content. The user scrolls down inside the modal to read it. Once they reach the bottom of the modal's content, the browser doesn't stop — it keeps interpreting further scroll input as scrolling the page underneath the modal, which the user usually can't even see. The page behind the modal silently scrolls out from under them. This is scroll chaining, and it's the default behavior everywhere unless you turn it off.
The fix: overscroll-behavior: contain
overscroll-behavior: contain on the scrollable element stops scroll chaining at its boundary — once the user reaches the end of that element's own content, further scroll input simply does nothing instead of propagating to the parent:.modal__body {
overflow-y: auto;
max-height: 70vh;
overscroll-behavior: contain;
}Worked example: a scrollable modal
<div class="modal-overlay">
<div class="modal">
<h2>Terms of Service</h2>
<div class="modal__body">
<!-- long scrollable content -->
</div>
</div>
</div>body {
overflow: hidden; /* page shouldn't scroll while modal is open anyway */
}
.modal__body {
overflow-y: auto;
max-height: 70vh;
overscroll-behavior: contain; /* stops chaining even if body scroll leaks */
}contain in place, scrolling to the end of the modal's content — in either direction — simply stops there. The page behind it never moves, and on touch devices the characteristic elastic "bounce" also stays local to the modal instead of bouncing the whole page.The three values
Value | Effect |
|---|---|
auto | Default — overscroll chains to the parent scrollable area (and eventually the page) |
contain | Stops chaining to the parent, but still shows the browser's own overscroll effects (e.g. bounce/glow) locally |
none | Stops chaining AND suppresses the browser's native overscroll effects entirely |
overscroll-behavior-x and overscroll-behavior-y — useful for a horizontally scrolling carousel that shouldn't chain sideways but is fine chaining vertically.overscroll-behavior only changes what happens once you run out of content to scroll — it's not a way to disable scrolling, and it has no effect on an element that isn't scrollable in the first place.overscroll-behavior-y: contain (or none) on html or body to kill the "pull to refresh" and rubber-band bounce effect at the very top/bottom of the page on mobile browsers, independent of any modal use case.