demo · v140

Overscroll Recipe Book

Four real-world patterns that rely on overscroll-behavior being set on :root or html and propagating to the viewport. Before Chrome 140 that propagation was broken — these patterns silently failed.

Pattern 1

No pull-to-refresh in a PWA

A standalone PWA must suppress the pull-to-refresh gesture. html { overscroll-behavior-y: none; } is the one-liner — but it only propagates to the viewport in Chrome 140+.

Before Chrome 140: setting on <html> was ignored; pull-to-refresh still triggered on the viewport overscroll.

Scroll me. Overscroll at the top — pull-to-refresh is suppressed.

This simulates html { overscroll-behavior-y: none; } propagated to the viewport.

The overscroll-behavior-y: none is applied to this frame to show the effect.

In the Chrome 138 simulation below, the root-level setting does nothing.

End of scrollable content.

overscroll-behavior-y: none — applied to root, propagates
/* works as of Chrome 140 */
html { overscroll-behavior-y: none; }
Pattern 2

Scroll-linked animation progress bar

A progress bar that fills as you scroll. Uses overscroll-behavior-y: none on root so elastic overscroll doesn't jerk the animation past 100%.

Before Chrome 140: overscroll rubber-band caused the animation to overshoot on Safari-like engines and on Chrome when the root setting didn't propagate.

Scroll down to animate the progress bar above. With Chrome 140 the bar stays clamped to 100% — no overscroll overshoot.

Paragraph two. Scroll further.

Paragraph three. Keep going.

Paragraph four.

Paragraph five.

Paragraph six — you're near the end.

Paragraph seven — bottom of content.

animation clamped — no overshoot
html { overscroll-behavior-y: none; }
/* scroll-driven animation then reads clean scroll position */
@keyframes grow { from { transform: scaleX(0) } to { transform: scaleX(1) } }
#progress-bar { animation: grow linear; animation-timeline: scroll(root); }
Pattern 3

Lock scroll while modal is open

When a modal dialog is open, background scroll should be locked. Toggle overscroll-behavior: contain on the body via a class, now that root propagation works.

Before Chrome 140: the root / html level contain didn't stop the viewport from scrolling when a touch gesture started on the modal backdrop.
modal closed — scroll unlocked
/* on modal open */
document.documentElement.style.overscrollBehavior = "none";
/* on modal close */
document.documentElement.style.overscrollBehavior = "";
Pattern 4

Custom pull-to-refresh indicator

Suppress the browser's built-in pull-to-refresh with overscroll-behavior-y: none on root, then show a custom indicator when the user drags down past the top.

Before Chrome 140: the browser PTR gesture fired even with html { overscroll-behavior-y: none; }. Developers had to add the rule to body as a workaround.

Scroll to the top of the frame below, then click "Simulate overscroll" to trigger the custom indicator:

↻ Custom refresh — release to refresh

Content item 1 — scroll to top and overscroll to trigger custom PTR.

Content item 2.

Content item 3.

Content item 4 — bottom of content.

overscroll-behavior-y: none on root — browser PTR suppressed
html { overscroll-behavior-y: none; }

window.addEventListener("touchstart", e => {
  if (window.scrollY === 0) startCustomPTR();
});

see also