CSS Overscroll Behavior
In this page:
selector {
overscroll-behavior: auto | contain | none;
}
Stopping Scroll Chaining with overscroll-behavior
By default, once you scroll a nested element (like a modal or sidebar) to its edge, further scroll input "chains" up to the parent page and keeps scrolling it too. overscroll-behavior: contain stops that hand-off, keeping the scroll gesture trapped inside the element it started in.
- Set overscroll-behavior: contain on chat windows and modals so users scrolling a long message list never accidentally scroll the page behind it.
- Accepted values:
- auto — normal scroll chaining (default)
- contain — stops chaining to the parent
- none — stops chaining and browser effects such as bounce
- Can be set per axis with overscroll-behavior-x and overscroll-behavior-y
- inherit | initial | unset | revert — CSS-wide keywords accepted by every property
Example: Stopping Scroll Chaining with overscroll-behavior
<style>
.modal {
overflow-y: auto;
height: 100px;
background: #f0f0f0;
border: 1px solid #ccc;
padding: 8px;
overscroll-behavior: contain;
}
</style>
<div class="modal">
<p>Long scrollable content that won't chain to the page.</p>
<p>Paragraph 1 of filler content to make this box scrollable.</p>
<p>Paragraph 2 of filler content to make this box scrollable.</p>
<p>Paragraph 3 of filler content to make this box scrollable.</p>
<p>Paragraph 4 of filler content to make this box scrollable.</p>
<p>Paragraph 5 of filler content to make this box scrollable.</p>
<p>Paragraph 6 of filler content to make this box scrollable.</p>
<p>Paragraph 7 of filler content to make this box scrollable.</p>
<p>Paragraph 8 of filler content to make this box scrollable.</p>
<p>Paragraph 9 of filler content to make this box scrollable.</p>
<p>Paragraph 10 of filler content to make this box scrollable.</p>
<p>...it won't scroll the page behind it.</p>
</div>
Axis-Specific Control with overscroll-behavior-x and -y
overscroll-behavior-x and overscroll-behavior-y let you contain scroll chaining on just one axis, leaving the other axis free to chain normally.
This is especially useful for horizontally-scrolling carousels, where you want to stop horizontal chaining but still allow the page to scroll vertically as usual.
- Use overscroll-behavior-x: contain on horizontal image carousels so swiping through images never triggers a vertical page scroll on mobile.
- Accepted values:
- auto — default scroll chaining (initial value)
- contain — no chaining to ancestors, keeps bounce effects
- none — no chaining and no bounce effect
- inherit | initial | unset | revert — CSS-wide keywords accepted by every property
Example: Axis-Specific Control with overscroll-behavior-x and -y
<style>
.carousel {
display: flex;
gap: 8px;
overflow-x: auto;
background: #f0f0f0;
border: 1px solid #ccc;
padding: 8px;
overscroll-behavior-x: contain;
}
.carousel .card {
flex: 0 0 120px;
height: 80px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
</style>
<div class="carousel">
<div class="card" style="background:#e74c3c">1</div>
<div class="card" style="background:#3498db">2</div>
<div class="card" style="background:#2ecc71">3</div>
<div class="card" style="background:#f1c40f">4</div>
<div class="card" style="background:#9b59b6">5</div>
<div class="card" style="background:#e67e22">6</div>
<div class="card" style="background:#1abc9c">7</div>
</div>
Preventing Layout Jumps with overflow-anchor
overflow-anchor controls scroll anchoring, a browser feature that automatically keeps your visual scroll position stable when content above the viewport changes size (like an image finishing loading).
Setting it to none disables this automatic correction, which is occasionally useful for chat apps deliberately pinning scroll to the bottom.
- Leave overflow-anchor at its default value of auto in almost all cases, since scroll anchoring is what prevents jarring jumps as a page loads.
- Accepted values:
- auto — scroll anchoring on (initial value)
- none — scroll anchoring off
- inherit | initial | unset | revert — CSS-wide keywords accepted by every property
Offsetting Snap Targets with scroll-margin
scroll-margin adds invisible space around an element that only affects where the browser stops scrolling when that element becomes a scroll-snap target -- it never changes the element's visible layout position like a normal margin would.
This is useful for leaving a gap between a snapped section and a fixed header that would otherwise overlap it.
- Set scroll-margin-top equal to your fixed header's height so scroll-snapped sections stop cleanly below the header instead of underneath it.
- Accepted values:
- length — e.g. 80px
- one to four values — top, right, bottom, left order
- 0 — no margin (initial value)
- scroll-margin-top and other side longhands
- inherit | initial | unset | revert — CSS-wide keywords accepted by every property
Example: Offsetting Snap Targets with scroll-margin
<style>
.container {
height: 150px;
overflow-y: scroll;
scroll-snap-type: y mandatory;
border: 1px solid #ccc;
}
.container section {
scroll-snap-align: start;
scroll-margin-top: 60px;
height: 120px;
padding: 10px;
box-sizing: border-box;
}
</style>
<div class="container">
<section id="s1" style="background:lightblue">Section 1 — stops cleanly below a 60px fixed header</section>
<section id="s2" style="background:lightgreen">Section 2 — snaps here next</section>
<section id="s3" style="background:lightyellow">Section 3 — last snap target</section>
</div>
Offsetting the Scroll Container with scroll-padding
scroll-padding works like scroll-margin but is set on the scrolling container itself rather than on the target elements inside it, shrinking the effective viewport used for scroll-snap calculations.
It is the container-side equivalent, useful when every snap target in a list needs the same consistent offset from the container edges.
- Use scroll-padding-top on the scroll container as an alternative to setting scroll-margin-top on every individual snap target inside it.
- Accepted values:
- length — e.g. 80px
- percentage — relative to the scrollport
- auto — no padding (initial value)
- one to four values — top, right, bottom, left order
- inherit | initial | unset | revert — CSS-wide keywords accepted by every property
Example: Offsetting the Scroll Container with scroll-padding
<style>
.container {
scroll-padding-top: 60px;
overflow-y: scroll;
scroll-snap-type: y mandatory;
height: 150px;
background: #f0f0f0;
border: 1px solid #ccc;
}
.container section {
scroll-snap-align: start;
height: 120px;
padding: 10px;
box-sizing: border-box;
}
</style>
<div class="container">
<section style="background:lightblue">Section 1 — container reserves 60px at the top via scroll-padding</section>
<section style="background:lightgreen">Section 2</section>
<section style="background:lightyellow">Section 3</section>
</div>
- Forgetting that overscroll-behavior only affects what happens once a scrollable element reaches its own edge, not scrolling in general.
- Using overscroll-behavior: none instead of contain, which also disables the native pull-to-refresh and bounce effects some users rely on.
- Applying scroll-margin instead of margin and expecting it to change layout spacing, when it only affects scroll-snap stopping position.
- overscroll-behavior (and its -x/-y variants) stops scroll chaining, so scrolling a nested box does not also scroll the page behind it.
- overflow-anchor prevents the jarring jump that happens when content loads above your current scroll position.
- scroll-margin and scroll-padding add invisible offset space around scroll-snap targets and scroll containers respectively.
overscroll-behavior and overflow-anchor are supported in all modern Chromium and Firefox browsers; Safari support for overscroll-behavior landed more recently, so always test on iOS Safari specifically.
Chapter Quiz — Complete all 14 topics to unlock
0/14 topics done
Complete these topics first: