Skip to content

fix(modal): prevent scroll chaining to the page on iOS Safari - #636

Merged
nonsobethel0-dev merged 2 commits into
Parashield-Protocol:mainfrom
presidoclintonbased-alt:fix/552-modal-scroll-chaining
Sep 23, 2026
Merged

nonsobethel0-dev merged 2 commits into
Parashield-Protocol:mainfrom
presidoclintonbased-alt:fix/552-modal-scroll-chaining

Conversation

@presidoclintonbased-alt

@presidoclintonbased-alt presidoclintonbased-alt commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Modal locks page scroll through useModalOverflow, which only sets body { overflow: hidden }. On iOS Safari that isn't enough:

  1. Nothing contained overscroll. When a scroll inside the modal hit its top or bottom, Safari chained it to the page, which rubber-banded or scrolled behind the modal.
  2. The dialog wasn't a scroll container. It had no max height and no overflow-y, so a tall modal on a phone (e.g. Buy Policy, Deposit) grew past the viewport and every touch-scroll went straight to the page.

Changes

Commit 1: page-level lock (useModalOverflow + globals.css)

  • While any modal lock is held, the hook adds a modal-scroll-locked class (exported as SCROLL_LOCK_CLASS) to <html>, and removes it once the last lock is released or resetModalOverflow() runs. It uses the same activeLocks set, so stacked modals still work.
  • globals.css: html.modal-scroll-locked, html.modal-scroll-locked body { overscroll-behavior: none; } stops the page itself from rubber-banding or chaining.
  • A class is used instead of inline styles so the rule lives in CSS and doesn't depend on reading back and restoring previous inline values.

Commit 2: dialog-level containment (Modal.tsx)

  • The dialog is now its own scroll area: max-h-[calc(100dvh-2rem)] overflow-y-auto overscroll-contain. dvh tracks iOS's collapsing toolbar, and 2rem matches the backdrop's p-4.
  • The backdrop also gets overscroll-contain.
  • I didn't use the issue's alternative, touch-action: none on the overlay. Browsers apply it to descendants as well, so it would also stop users scrolling inside the modal.

Tests

  • useModalOverflow.test.ts: the class is present only while at least one lock is held (checked with two stacked locks), and resetModalOverflow() clears it.
  • Modal.test.tsx: the dialog and backdrop have the containment and viewport-cap classes.

Verification

useModalOverflow now toggles a modal-scroll-locked class on <html>
alongside body overflow:hidden; globals.css sets overscroll-behavior:
none on html/body under it so iOS Safari can't chain scroll or
rubber-band the page behind an open modal.
Cap the dialog to the viewport (100dvh - 2rem) with overflow-y-auto and
overscroll-contain, and contain overscroll on the backdrop, so tall
modals scroll internally and hitting the top/bottom never chains to the
page on iOS Safari. Add a Modal test for the containment classes.
@drips-wave

drips-wave Bot commented Sep 23, 2026

Copy link
Copy Markdown

@presidoclintonbased-alt Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@netlify

netlify Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

❌ Deploy Preview for boisterous-sunshine-dd4c4c failed.

Name Link
🔨 Latest commit 4991a4e
🔍 Latest deploy log https://app.netlify.com/projects/boisterous-sunshine-dd4c4c/deploys/6ab42a6906ece400088ccb7a

@nonsobethel0-dev
nonsobethel0-dev merged commit e02bba1 into Parashield-Protocol:main Sep 23, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants