usePreventScroll's iOS keyboard reveal fails under React >= 19.3 in Next.js 16.3
- Dominant language
- TypeScript
- Stars
- 15.9k
- Forks
- 1.6k
- Avg merge
- 3d 9m
- Merged PRs (30d)
- 59
Description
### Provide a general summary of the issue here
On iOS Safari, tapping a text field inside a Modal that sizes itself from --visual-viewport-height leaves the field hidden behind the keyboard. Nothing scrolls it into view, and nothing errors.
When the keyboard opens, Safari fires one resize event on visualViewport, and two listeners run one after the other:
- useViewportSize, added by react-aria-components on Modal mount. It calls setState, which writes --visual-viewport-height, which grows the sheet's padding and makes the body overflow.
- usePreventScroll's reveal, added when the user tapped the field. It measures the scroller straight away and decides how far to scroll.
So the reveal only works if React commits the first listener's update before the second listener runs. That comes down to which bucket getEventPriority puts resize in.
- react-dom 19.2 and earlier group resize with click. The update flushes in the microtask between the two listeners, so the reveal measures a scroller that has already shrunk and overflows. It scrolls correctly.
- The React 19.3 canary moves resize in with scroll and mousemove (facebook/react#35117). The update now flushes after the whole event finishes. The reveal measures a scroller that has not shrunk yet, so `scrollHeight - clientHeight` is still 0, the scroll target gets clamped to 0, and `scrollTo({top: 0})` does nothing.
### 🤔 Expected Behavior?
The focused input is correctly scrolled into view
### 😯 Current Behavior
See above
### 💁 Possible Solution
I have solved this currently by attaching a visualViewport resize listener, and setting the `--visual-viewport-height` property directly on the overlay DOM node.
```ts
const onResize = () => {
overlayRef.current?.style.setProperty("--visual-viewport-height", `${visualViewport.height}px`);
};
```
### 🔦 Context
I have built a RAC Modal sheet component for mobile users. The sheet can contain inputs, and sometimes enough inputs that the sheet can scroll, especially when the keyboard opens. The expectation is that when you focus an input and then the keyboard opens, thereby making the input end up out of the screen due to scroll, the input is scrolled back into view so the user can see what they are typing
### 🖥️ Steps to Reproduce
- [Repro nextjs (react >= 19.3) with issue](https://react-aria-keyboard-reveal-issue.vercel.app)
- [Repro vite (react < 19.3) without issue](https://react-aria-keyboard-reveal-issue-cu.vercel.app)
[Link to repository](https://github.com/nkalpak/react-aria-keyboard-reveal-issue)
[Link to nextjs sheet](https://github.com/nkalpak/react-aria-keyboard-reveal-issue/blob/main/next-repro/app/SheetDemo.jsx)
[Link to vite sheet](https://github.com/nkalpak/react-aria-keyboard-reveal-issue/blob/main/vite-repro/src/SheetDemo.jsx)
### Version
1.17.0
### What browsers are you seeing the problem on?
Safari
### If other, please specify.
_No response_
### What operating system are you using?
iOS
### 🧢 Your Company/Team
_No response_
### 🕷 Tracking Issue
_No response_
Contributor guide
Research direction
Start with the linked Next.js reproduction at next-repro/app/SheetDemo.jsx, then compare it with vite-repro/src/SheetDemo.jsx. Read the useViewportSize and usePreventScroll resize/reveal paths and reproduce the issue on iOS Safari with React 19.3 or later. Done means the focused input is scrolled into view when the keyboard opens without breaking the earlier React behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, react, typescript
- Domain
- accessibility, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100