← Back to CSS Course | Chapter 5: Layout & Display | Lesson 13 of 14

CSS Overscroll Behavior

Have you ever scrolled to the bottom of a popup chat box or comment list, and once it ran out of content, felt the entire page behind it start scrolling too? That unwanted hand-off is called scroll chaining. CSS overscroll properties let you contain scrolling inside a box, control which element "owns" the scroll position during layout shifts, and add breathing room around elements that scroll-snap targets.

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.

Note: Set overscroll-behavior: contain on chat windows and modals so users scrolling a long message list never accidentally scroll the page behind it.

Warning: overscroll-behavior: none goes further than contain by also disabling native bounce/refresh effects, which can feel broken on mobile browsers.

Example: Stopping Scroll Chaining with overscroll-behavior

css
<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.

Note: Use overscroll-behavior-x: contain on horizontal image carousels so swiping through images never triggers a vertical page scroll on mobile.

Warning: Setting only overscroll-behavior-x leaves the y-axis at its default value of auto, so vertical chaining will still happen unless set separately.

Example: Axis-Specific Control with overscroll-behavior-x and -y

css
<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.

Note: 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.

Warning: Disabling overflow-anchor globally can reintroduce the classic "page jumped because an image above loaded" bug that scroll anchoring exists to fix.

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.

Note: Set scroll-margin-top equal to your fixed header's height so scroll-snapped sections stop cleanly below the header instead of underneath it.

Warning: scroll-margin has no visible effect at all outside of a scroll-snap context, so do not use it as a substitute for regular margin spacing.

Example: Offsetting Snap Targets with scroll-margin

css
<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.

Note: Use scroll-padding-top on the scroll container as an alternative to setting scroll-margin-top on every individual snap target inside it.

Warning: Mixing scroll-padding on the container with conflicting scroll-margin values on children can produce confusing, hard-to-predict snap offsets.

Example: Offsetting the Scroll Container with scroll-padding

css
<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>
Common Mistakes
  1. Forgetting that overscroll-behavior only affects what happens once a scrollable element reaches its own edge, not scrolling in general.
  2. Using overscroll-behavior: none instead of contain, which also disables the native pull-to-refresh and bounce effects some users rely on.
  3. Applying scroll-margin instead of margin and expecting it to change layout spacing, when it only affects scroll-snap stopping position.
Chapter Summary
  • 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.
Browser Support

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.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.