The overscroll-behavior property controls what happens when a scroll boundary is reached. Here's how to use it.
Basic Usage#
1/* Prevent scroll chaining */
2.modal {
3 overscroll-behavior: contain;
4}
5
6/* Values */
7.auto {
8 overscroll-behavior: auto; /* Default browser behavior */
9}
10
11.contain {
12 overscroll-behavior: contain; /* Prevent scroll chaining */
13}
14
15.none {
16 overscroll-behavior: none; /* No scroll chaining, no bounce */
17}
18
19/* Individual axes */
20.horizontal {
21 overscroll-behavior-x: contain;
22 overscroll-behavior-y: auto;
23}
24
25.vertical {
26 overscroll-behavior-x: auto;
27 overscroll-behavior-y: contain;
28}Preventing Scroll Chaining#
1/* Modal that doesn't scroll parent */
2.modal-overlay {
3 position: fixed;
4 inset: 0;
5 background: rgba(0, 0, 0, 0.5);
6 overflow: auto;
7}
8
9.modal-content {
10 max-height: 80vh;
11 overflow-y: auto;
12 overscroll-behavior: contain;
13}
14
15/* Sidebar scroll isolation */
16.sidebar {
17 height: 100vh;
18 overflow-y: auto;
19 overscroll-behavior-y: contain;
20}
21
22/* Chat message list */
23.message-list {
24 flex: 1;
25 overflow-y: auto;
26 overscroll-behavior: contain;
27}
28
29/* Dropdown menu */
30.dropdown-menu {
31 max-height: 300px;
32 overflow-y: auto;
33 overscroll-behavior: contain;
34}Pull-to-Refresh Control#
1/* Disable pull-to-refresh on mobile */
2body {
3 overscroll-behavior-y: contain;
4}
5
6/* Allow on specific areas */
7.refreshable-content {
8 overscroll-behavior-y: auto;
9}
10
11/* Prevent refresh but allow bounce */
12.no-refresh {
13 overscroll-behavior-y: contain;
14}
15
16/* No bounce at all */
17.no-bounce {
18 overscroll-behavior-y: none;
19}
20
21/* Custom pull-to-refresh container */
22.ptr-container {
23 overscroll-behavior-y: contain;
24 /* Implement custom PTR with JS */
25}Navigation Gestures#
1/* Prevent back/forward swipe gestures */
2body {
3 overscroll-behavior-x: contain;
4}
5
6/* Carousel that doesn't trigger navigation */
7.carousel {
8 display: flex;
9 overflow-x: auto;
10 overscroll-behavior-x: contain;
11 scroll-snap-type: x mandatory;
12}
13
14.carousel-item {
15 flex: 0 0 100%;
16 scroll-snap-align: start;
17}
18
19/* Horizontal scrolling section */
20.horizontal-scroll {
21 overflow-x: auto;
22 overscroll-behavior-x: contain;
23 -webkit-overflow-scrolling: touch;
24}
25
26/* Image gallery */
27.gallery {
28 display: flex;
29 overflow-x: auto;
30 overscroll-behavior-x: contain;
31 gap: 1rem;
32 padding: 1rem;
33}Nested Scroll Containers#
1/* Outer container scrolls normally */
2.outer-scroll {
3 height: 100vh;
4 overflow-y: auto;
5}
6
7/* Inner container doesn't chain */
8.inner-scroll {
9 height: 300px;
10 overflow-y: auto;
11 overscroll-behavior: contain;
12}
13
14/* Multiple nested levels */
15.level-1 {
16 overflow: auto;
17}
18
19.level-2 {
20 overflow: auto;
21 overscroll-behavior: contain;
22}
23
24.level-3 {
25 overflow: auto;
26 overscroll-behavior: contain;
27}
28
29/* Card with scrollable content */
30.card {
31 max-height: 400px;
32 overflow: hidden;
33}
34
35.card-body {
36 max-height: 300px;
37 overflow-y: auto;
38 overscroll-behavior-y: contain;
39}Mobile App Feel#
1/* App-like scrolling */
2html, body {
3 height: 100%;
4 overflow: hidden;
5}
6
7.app-container {
8 height: 100%;
9 display: flex;
10 flex-direction: column;
11}
12
13.app-header {
14 flex-shrink: 0;
15}
16
17.app-content {
18 flex: 1;
19 overflow-y: auto;
20 overscroll-behavior-y: contain;
21 -webkit-overflow-scrolling: touch;
22}
23
24.app-footer {
25 flex-shrink: 0;
26}
27
28/* Prevent rubber-banding on iOS */
29.ios-fix {
30 position: fixed;
31 width: 100%;
32 height: 100%;
33 overflow: hidden;
34}
35
36.ios-fix .scroll-content {
37 height: 100%;
38 overflow-y: auto;
39 overscroll-behavior-y: none;
40}Drawer and Sheet Components#
1/* Bottom sheet */
2.bottom-sheet {
3 position: fixed;
4 bottom: 0;
5 left: 0;
6 right: 0;
7 max-height: 80vh;
8 overflow-y: auto;
9 overscroll-behavior-y: contain;
10 border-radius: 16px 16px 0 0;
11 background: white;
12}
13
14/* Side drawer */
15.drawer {
16 position: fixed;
17 top: 0;
18 left: 0;
19 width: 280px;
20 height: 100vh;
21 overflow-y: auto;
22 overscroll-behavior-y: contain;
23 background: white;
24}
25
26/* Slide-up panel */
27.slide-panel {
28 position: fixed;
29 bottom: 0;
30 left: 0;
31 right: 0;
32 height: 60vh;
33 overflow-y: auto;
34 overscroll-behavior: contain;
35}Form Containers#
1/* Long form in modal */
2.form-modal {
3 max-height: 90vh;
4 overflow-y: auto;
5 overscroll-behavior: contain;
6}
7
8/* Scrollable fieldset */
9.fieldset-scroll {
10 max-height: 200px;
11 overflow-y: auto;
12 overscroll-behavior: contain;
13 padding: 1rem;
14 border: 1px solid #ddd;
15}
16
17/* Autocomplete dropdown */
18.autocomplete-list {
19 max-height: 250px;
20 overflow-y: auto;
21 overscroll-behavior: contain;
22 border: 1px solid #ddd;
23 border-top: none;
24}
25
26/* Multi-select options */
27.select-options {
28 max-height: 200px;
29 overflow-y: auto;
30 overscroll-behavior: contain;
31}Data Tables#
1/* Scrollable table wrapper */
2.table-wrapper {
3 max-height: 500px;
4 overflow: auto;
5 overscroll-behavior: contain;
6}
7
8/* Horizontal scroll table */
9.table-horizontal {
10 overflow-x: auto;
11 overscroll-behavior-x: contain;
12}
13
14/* Both directions */
15.table-both {
16 overflow: auto;
17 overscroll-behavior: contain;
18}
19
20/* Fixed header table */
21.fixed-header-table {
22 position: relative;
23}
24
25.fixed-header-table thead {
26 position: sticky;
27 top: 0;
28 z-index: 1;
29}
30
31.fixed-header-table tbody {
32 overflow-y: auto;
33 overscroll-behavior-y: contain;
34}Code Editor Pattern#
1/* Code editor scroll */
2.code-editor {
3 display: flex;
4 height: 400px;
5}
6
7.line-numbers {
8 flex-shrink: 0;
9 overflow: hidden;
10}
11
12.code-content {
13 flex: 1;
14 overflow: auto;
15 overscroll-behavior: contain;
16}
17
18/* Terminal output */
19.terminal {
20 height: 300px;
21 overflow-y: auto;
22 overscroll-behavior-y: contain;
23 background: #1e1e1e;
24 color: #d4d4d4;
25 font-family: monospace;
26}
27
28/* Log viewer */
29.log-viewer {
30 height: 100%;
31 overflow-y: auto;
32 overscroll-behavior: contain;
33}JavaScript Integration#
1// Check support
2const supportsOverscroll = CSS.supports('overscroll-behavior', 'contain');
3
4// Apply dynamically
5function preventScrollChain(element) {
6 element.style.overscrollBehavior = 'contain';
7}
8
9// Toggle based on state
10function setModalOpen(isOpen) {
11 document.body.style.overflow = isOpen ? 'hidden' : '';
12 modal.style.overscrollBehavior = isOpen ? 'contain' : 'auto';
13}
14
15// Custom scroll handling
16element.addEventListener('scroll', (e) => {
17 const { scrollTop, scrollHeight, clientHeight } = e.target;
18 const atTop = scrollTop === 0;
19 const atBottom = scrollTop + clientHeight >= scrollHeight;
20
21 if (atTop || atBottom) {
22 // At boundary - overscroll-behavior handles chaining
23 }
24});Best Practices#
Usage:
✓ Use contain for modals/overlays
✓ Use for isolated scroll areas
✓ Prevent unwanted navigation gestures
✓ Control pull-to-refresh
Values:
✓ auto: default browser behavior
✓ contain: prevent chaining only
✓ none: no chaining + no effects
Patterns:
✓ Modal dialogs
✓ Dropdown menus
✓ Side drawers
✓ Nested scroll containers
Avoid:
✗ Using on document body without reason
✗ Removing all scroll feedback
✗ Breaking expected mobile behaviors
✗ Forgetting touch scrolling
Conclusion#
The overscroll-behavior property controls scroll chaining and boundary effects. Use contain to prevent scroll events from propagating to parent containers, and none to also disable bounce effects. It's essential for modals, dropdowns, and any nested scrolling containers to prevent unexpected scroll behavior.