Skip to main content
  • Created:
  • Updated:
  • Author:
    Takeshi Takatsudo

Overscroll Behavior

The Problem

When a user scrolls to the end of a nested scrollable area — a modal, sidebar, dropdown, or chat panel — the browser "chains" the scroll event to the nearest scrollable ancestor. The background page suddenly starts scrolling beneath the overlay. This is called scroll chaining, and it is one of the most common UX bugs in web applications.

Before overscroll-behavior, preventing this required JavaScript scroll-locking hacks: listening for wheel events, calculating scroll positions, and calling preventDefault() at the right moment. These solutions were fragile, caused jank, and often broke touch device scrolling entirely. On mobile, overscroll effects like pull-to-refresh could also trigger unexpectedly inside custom scroll areas.

The Solution

The overscroll-behavior property gives you single-line CSS control over what happens when a scroll container reaches its boundary.

  • auto (default): Normal behavior — scroll chains to the parent and native overscroll effects (bounce, pull-to-refresh) are active.
  • contain: Prevents scroll chaining to the parent, but native overscroll effects (like the rubber-band bounce on iOS/macOS) still apply within the element itself.
  • none: Prevents both scroll chaining and all native overscroll effects. The scroll simply stops.

You can also control each axis independently with overscroll-behavior-x and overscroll-behavior-y.

Core Principles

auto (Default)

The default value. Scrolling chains to the parent when the element reaches its scroll boundary. This is usually fine for the main page content but causes problems in overlays, modals, and sidebars.

contain

The most commonly needed value. It prevents scroll chaining so that scrolling stays trapped inside the element. Use this on any independently scrollable region — modals, sidebars, chat panels, dropdown menus — where you do not want the background to scroll.

none

Goes further than contain by also suppressing native overscroll effects like the rubber-band bounce or pull-to-refresh. Use this when you want scrolling to stop completely at the boundary with no visual feedback. Useful for embedded app-like interfaces.

Scroll Chaining Problem (no fix)
Scroll Chaining Fixed with overscroll-behavior: contain
contain vs none — Side by Side
Chat Panel with Contained Scroll
Sidebar Navigation with Contained Scroll

Code Examples

Preventing Scroll Chaining on a Modal

.modal-body {
max-height: 80vh;
overflow-y: auto;
overscroll-behavior-y: contain;
}

This single line prevents the background page from scrolling when the user scrolls to the end of the modal content.

Preventing Accidental Browser Back Navigation

.horizontal-scroller {
overflow-x: auto;
overscroll-behavior-x: contain;
}

On some browsers, a horizontal overscroll gesture triggers browser back/forward navigation. Setting overscroll-behavior-x: contain on horizontal scroll areas prevents this accidental navigation.

App Shell with Contained Panels

.app-shell {
display: grid;
grid-template-columns: 250px 1fr 300px;
height: 100vh;
}

.sidebar-nav {
overflow-y: auto;
overscroll-behavior: contain;
}

.main-content {
overflow-y: auto;
}

.detail-panel {
overflow-y: auto;
overscroll-behavior: contain;
}

In a multi-panel layout, apply overscroll-behavior: contain to each secondary scrollable panel so they do not chain scroll into the main content area.

Disabling Pull-to-Refresh

body {
overscroll-behavior-y: none;
}

On mobile browsers, pulling down at the top of the page triggers a refresh. Setting overscroll-behavior-y: none on the body disables this behavior — useful for web apps that have their own refresh mechanism.

Common AI Mistakes

  • Not suggesting overscroll-behavior at all: Defaulting to JavaScript scroll-locking libraries or event.preventDefault() hacks when a single CSS property solves the problem.
  • Applying it to the wrong element: overscroll-behavior must be set on the element that has the scroll (overflow: auto or overflow: scroll), not on a parent or wrapper.
  • Always using none instead of contain: Using none suppresses all overscroll feedback, which can feel unnatural. Prefer contain unless you specifically need to suppress bounce effects.
  • Forgetting the axis-specific variants: Using overscroll-behavior: contain when only one axis needs containment, like overscroll-behavior-x: contain for a horizontal scroller.
  • Not combining with overflow: overscroll-behavior only takes effect on scroll containers. If the element does not have overflow: auto or overflow: scroll, the property has no effect.

When to Use

  • Modals and dialogs: Prevent background page scroll when scrolling inside a modal.
  • Sidebars and navigation panels: Keep sidebar scroll independent from the main content.
  • Chat and messaging panels: Prevent the page from scrolling when users scroll through message history.
  • Dropdown menus: Long dropdowns should not scroll the page behind them.
  • Horizontal scroll areas: Prevent accidental browser back/forward navigation with overscroll-behavior-x: contain.
  • Mobile web apps: Disable pull-to-refresh with overscroll-behavior-y: none on the body when the app handles its own refresh logic.

References