The Problem

When building a modal, drawer, or bottom sheet, the standard approach is to add overflow: hidden to the <body> while the overlay is open. This works in every browser except iOS — where the body remains freely scrollable underneath the overlay regardless of which browser you use.

scrolls!

Page scrolls behind modal overlay

Root Cause

WebKit on iOS manages page scroll through a native UIScrollView — a system-level component that bypasses CSS entirely. The overflow: hidden property targets CSS rendering but has no effect on the native scroll layer that UIScrollView controls.

This bug has been tracked as WebKit Bug #153852 since 2015 and has never been fixed. The iOS team considers native-feel scroll to be a feature, not a bug.

Why this matters: Users can two-finger-scroll (on iPad) or single-finger-swipe the background page even when a full-screen modal is visually in front of it, creating a disorienting experience.

Fix 1 — touchmove preventDefault (Recommended)

Intercept touch events on the overlay and call preventDefault() on swipes that aren't inside the scrollable modal content. This directly blocks the native scroll event.

modal.js
const preventScroll = (e) => {
  // Allow scroll inside the modal content itself
  if (!e.target.closest('.modal-content')) {
    e.preventDefault();
  }
};

// When modal opens:
document.addEventListener('touchmove', preventScroll, { passive: false });

// When modal closes:
document.removeEventListener('touchmove', preventScroll);
Important: { passive: false } is required. Modern browsers register touchmove listeners as passive by default for performance, which means preventDefault() is ignored. You must explicitly opt out of passive mode to be able to cancel the event.

Fix 2 — position: fixed (Simpler, with tradeoff)

A simpler alternative that actually works on iOS: freeze the body in place using position: fixed. The tradeoff is a scroll-position jump — you must save and restore the scroll position manually.

modal-fixed.js
let scrollY = 0;

function openModal() {
  scrollY = window.scrollY;
  document.body.style.cssText = `
    position: fixed;
    top: -${scrollY}px;
    width: 100%;
    overflow: hidden;
  `;
}

function closeModal() {
  document.body.style.cssText = '';
  window.scrollTo(0, scrollY);
}

Browser Support

BrowserMin VersionStatusNotes
Safari iOSAllNot supportedoverflow:hidden on body is ignored by WebKit (bug #153852)
Chrome iOSAllNot supportedUses same WebKit engine — same behaviour
Chrome AndroidAllSupportedoverflow:hidden on body works correctly
FirefoxAllSupportedoverflow:hidden on body works correctly
Safari macOSAllSupportedOnly iOS is affected

Further Reading