-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix: Improve behavior of modals on iOS 26 #8888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Build successful! 🎉 |
data-testid="underlay" | ||
{...otherProps} | ||
// Cover the entire document so iOS 26 Safari doesn't clip the underlay to the inner viewport. | ||
style={{height: isOpen && typeof document !== 'undefined' ? document.body.clientHeight : undefined}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assumes the clientHeight won't change much while a modal/tray is open. Otherwise we have to put a resize observer I guess, and that would be unfortunate...
// Make the whole dialog scroll rather than only the content when the height it small. | ||
[`@media (height < ${400 / 16}rem)`]: 'visible' | ||
// Make the whole dialog scroll rather than only the content when the height is small. | ||
[`@container (height < ${500 / 16}rem)`]: 'visible' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using container queries for this because when the keyboard opens the visual viewport resizes but the media query doesn't update.
<article | ||
<div | ||
className={style({ | ||
isolation: 'isolate', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new div to add isolation: isolate. This makes the sticky header appear behind dialogs.
max-height: var(--visual-viewport-height); | ||
top: calc(var(--visual-viewport-height) / 2); | ||
left: 50%; | ||
translate: -50% -50%; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kind of a weird hack to center the modal without adding an extra div wrapper to the example...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm running into some buggy problems in https://reactspectrum.blob.core.windows.net/reactspectrum/4971777a6c845fb8eeadce7a21a96ac76eac44f8/docs/react-aria/index.html
Particularly when i tap in the Date Planted then try to open the date picker inside the create new dialog. Or try to use any of the other controls. I'm unsure what is an iOS 26 bug vs ours. Like the text cursor definitely seems like a Safari problem...
It's all so... floaty and see through, sometimes it just looks like a bug when it's probably how it's supposed to be... like that url bar that floats so high above the keyboard....
Edit: compared to main, the cursor used to be ok for some reason? but the names were always cutoff
In this next image, i had tried to open the DatePicker and was unsucessful, wouldn't open in my viewport.
@devongovett I tested it out on iOS 18.6.2, looks like there's still the issue with the overscrolling. dialog-encoded.mp4Thanks for working on this by the way! 🙏 |
Thanks for testing. @snowystinger yeah, the issue with the small address bar on top of the modal seems to be a Safari issue. It occurs specifically with inputs that have @jeffijoe looks like maybe the over scrolling happens when tapping on the whitespace in the date picker? If you tap on a segment itself it seems ok. I guess that's due to programmatic focus rather than the browser default tap behavior. I'll see if there's anything I can do about that. |
@devongovett the over-scrolling also happens for me in the current build when focusing any element (e.g. just a regular input) that is near the bottom, if that helps. |
@jeffijoe Thanks. I added a commit to clamp the scroll position which should prevent that. I also tried to fix the programmatic scrolling issue when tapping the date field. |
Build successful! 🎉 |
@devongovett just tried the latest build and the modal example on the main site is so much better now! 🙏 |
Ah, I found a way to fix the mis-positioned address bar issue too! Just needed to copy all the attributes that might affect the height of the keyboard (e.g. |
Build successful! 🎉 |
Build successful! 🎉 |
Build successful! 🎉 |
Build successful! 🎉 |
## API Changes
@react-aria/utils/@react-aria/utils:willOpenKeyboard+willOpenKeyboard {
+ target: Element
+ returnVal: undefined
+} |
I think I managed to remove the hidden input hack entirely? Turns out if you preemptively focus the real input with |
@devongovett I do! Here's a recording of the current behavior from the latest build. Looks like the scroll behavior has been fixed. The only issues I see:
I'm just happy the scroll and focus issues are resolved! 🙏 When do you think this will be released? 👀 modal-encoded.mp4 |
Thanks! Yeah the up/down arrows only work with native inputs, not with our Select. We have a hidden input in there but seems like Safari requires it to be visible. Not sure there's much we can do about that unfortunately. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is so much better!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Behavior seems good on Android Chrome and desktop, approving for deeper testing on Monday. The Chromatic caught some strange behaviors that I'm actually unable to reproduce locally so I'd be fine accepting those as the new baselines if we discover that those are truly just chromatic specific.
Fixes #7902, fixes #7972, fixes #5926
iOS 26 viewport changes
iOS Safari 26 significantly changes how the visual viewport works due to the controls that now overlay on top of the page.
position: fixed
elements are clipped to the viewport below the status bar and above the address bar, but other content continues to scroll behind them. This means that the modal backdrop will not fill the entire visible viewport, which looks strange.To fix this, we can use
position: absolute
for the backdrop instead offixed
, and make it cover the entire page instead of just the visual viewport. The actual modal is still sized within the visual viewport. For trays, we add additional padding that extends behind the address bar so it looks seamless.I also made
useViewportSize
update during theblur
event instead of waiting for theresize
event, which occurs after the keyboard animation is complete. This makes it feel a bit faster.usePreventScroll improvements
The usePreventScroll logic on iOS sometimes causes flickering when tapping on an input as we temporarily applied a transform to trick safari into not scrolling. Turns out we can avoid this by preemptively calling
relatedTarget.focus({preventScroll: true})
during theblur
event, and then scrolling ourselves.In iOS 26, we can also no longer apply
overscroll-behavior: contain
during thetouchstart
event, it must be applied before that. So now we inject a small<style>
element to ensure this is applied everywhere whileusePreventScroll
is active.I also improved the scroll into view logic so that it centers the input within the viewport, and smoothly animates into view just like the native implementation does (just without scrolling the entire page).
ScreenRecording_09-17-2025.21-18-33_1.MP4