How Pull to Refresh Works in iOS 15 Safari

With the release of iOS 15, Apple introduced a native pull-to-refresh gesture to Safari, bringing the mobile browser in line with the behavior of native iOS applications. This article breaks down how the gesture triggers at the system level, how it interacts with the Document Object Model (DOM), and the methods web developers use to adapt to or disable this feature.

The Native Trigger Mechanism

In iOS 15 Safari, pull-to-refresh activates when a user scrolls down while already at the top of a webpage.

  1. Scroll Position Check: The browser checks if the root viewport's vertical scroll position (window.scrollY) is at 0.
  2. Overscroll Detection: If the user performs a continuous downward swipe gesture starting from the top, Safari enters an elastic overscroll state, pulling the web page content down.
  3. Indicator Appearance: A native circular activity indicator appears above the top edge of the page.
  4. Reload Execution: If the user drags the page past a predefined vertical threshold and releases, Safari issues a standard page reload (location.reload()). If the user releases before reaching the threshold, the page snaps back to the top without refreshing.

Interaction with Web Content

Safari distinguishes between the root document and nested scrollable containers.

  • Root Document: Swiping down at the top of the <body> or <html> element will trigger the native refresh mechanism.
  • Internal Scroll Areas: If a user scrolls inside an element with overflow-y: scroll or overflow-y: auto, pull-to-refresh does not trigger as long as the scroll event remains isolated within that container. However, if the container reaches its top boundary and scroll chaining occurs, the gesture can bubble up to the viewport and initiate a refresh.
  • Fixed Elements: Elements styled with position: fixed move downward alongside the rest of the document during the overscroll bounce before snapping back.

Developer Control and Disabling the Feature

For web applications (such as Progressive Web Apps, canvas games, or sites with custom pull-to-refresh implementations), this native gesture can cause unintended page reloads.

CSS Prevention

In modern versions of Safari, the standard way to disable pull-to-refresh is the CSS overscroll-behavior property:

html, body {
  overscroll-behavior-y: none;
}

Setting overscroll-behavior-y: none prevents the overscroll glow, bounce, and the associated refresh action while leaving normal scrolling intact.

JavaScript Event Prevention

Because early iOS 15 builds had limited support for overscroll-behavior, developers also relied on intercepting touch events. By tracking the touchstart and touchmove events, you can prevent the default browser behavior if a downward swipe occurs at the top of the page:

let touchStartY = 0;

window.addEventListener('touchstart', (e) => {
  touchStartY = e.touches[0].clientY;
}, { passive: false });

window.addEventListener('touchmove', (e) => {
  const touchCurrentY = e.touches[0].clientY;
  if (window.scrollY === 0 && touchCurrentY > touchStartY) {
    e.preventDefault();
  }
}, { passive: false });

Setting { passive: false } is required, as Safari defaults touch event listeners to passive, which disables the ability to call e.preventDefault().