Skip to content

Conversation

devongovett
Copy link
Member

@devongovett devongovett commented Sep 18, 2025

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.

React Aria

To fix this, we can use position: absolute for the backdrop instead of fixed, 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 the blur event instead of waiting for the resize 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 the blur event, and then scrolling ourselves.

In iOS 26, we can also no longer apply overscroll-behavior: contain during the touchstart event, it must be applied before that. So now we inject a small <style> element to ensure this is applied everywhere while usePreventScroll 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

@rspbot
Copy link

rspbot commented Sep 18, 2025

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}}
Copy link
Member Author

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'
Copy link
Member Author

@devongovett devongovett Sep 18, 2025

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',
Copy link
Member Author

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%;
Copy link
Member Author

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...

Copy link
Member

@snowystinger snowystinger left a 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....

Image

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.

Image

@jeffijoe
Copy link
Contributor

@devongovett I tested it out on iOS 18.6.2, looks like there's still the issue with the overscrolling.

dialog-encoded.mp4

Thanks for working on this by the way! 🙏

@devongovett
Copy link
Member Author

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 autocorrect="off", which is why it doesn't happen for the "Scientific Name" field but does for the ComboBox and DatePicker. Safari seems to be calculating the position as if the autocorrect bar above the keyboard was there, and after the animation firing a second visual viewport resize (which is why it jumps a little). And the address bar just remains mis-positioned. Guessing that's why the cursor is also mis-positioned. Not sure there's much we can do there unfortunately. I'll see if a webkit bug got reported yet.

@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.

@jeffijoe
Copy link
Contributor

@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.

@devongovett
Copy link
Member Author

@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.

@rspbot
Copy link

rspbot commented Sep 18, 2025

@jeffijoe
Copy link
Contributor

@devongovett just tried the latest build and the modal example on the main site is so much better now! 🙏

@devongovett
Copy link
Member Author

devongovett commented Sep 18, 2025

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. autocorrect, inputMode, etc.) over to the temporary input.

@rspbot
Copy link

rspbot commented Sep 18, 2025

@rspbot
Copy link

rspbot commented Sep 18, 2025

@rspbot
Copy link

rspbot commented Sep 18, 2025

@rspbot
Copy link

rspbot commented Sep 18, 2025

@rspbot
Copy link

rspbot commented Sep 18, 2025

## API Changes

@react-aria/utils

/@react-aria/utils:willOpenKeyboard

+willOpenKeyboard {
+  target: Element
+  returnVal: undefined
+}

@devongovett
Copy link
Member Author

I think I managed to remove the hidden input hack entirely? Turns out if you preemptively focus the real input with{preventScroll: true} it will disable Safari's default behavior. Does anyone still have iOS 18 or earlier versions to test this version?

@jeffijoe
Copy link
Contributor

jeffijoe commented Sep 18, 2025

@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:

  • The image don't seem to allow navigating from the 2nd input to the 3rd dropdown
  • The popovers (e.g. the calendar) are slower to adjust their positioning than the modal itself

I'm just happy the scroll and focus issues are resolved! 🙏 When do you think this will be released? 👀

modal-encoded.mp4

@devongovett
Copy link
Member Author

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.

Copy link
Member

@snowystinger snowystinger left a 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!

Copy link
Member

@LFDanLu LFDanLu left a 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants