Repository navigation
feat: Overlay focus primitive - #5175
Draft
alpharius-ck wants to merge 5 commits into
Draft
alpharius-ck wants to merge 5 commits into
alpharius-ck wants to merge 5 commits into
Conversation
added 3 commits
November 6, 2026 12:00
Opening a modal now moves focus to its content, and closing it returns focus to whatever had it before. On web the first tabbable element is focused, since only the DOM can work out what that is; on native an accessibility focus event is sent instead. Focus waits for the fade in either direction: the content is not in the accessibility tree until it is opaque, and the app behind the modal only stops being inert once the modal has gone. Adds `initialFocusRef` and `restoreFocusRef` so an app can name the element to focus on open and the control to return to on close.
`useOverlayFocus` now returns `restoreFocus`, so the overlay decides when focus goes back rather than having it fire from an unmount cleanup that only ran once the content was already gone. Restoring waits a tick, so the close render has removed background inertness before the previous element is focused again. Reopening mid-close keeps the element that first opened the overlay, rather than capturing something inside the overlay that is still on its way out. Each focus move now reports whether it landed, through a web half that takes a DOM node and a native half that takes a ref, so an overlay that cannot take focus no longer leaves the hook believing focus moved.
|
Found potential problems with the pull request:
|
This was referenced Nov 6, 2026
Collaborator
|
#5127 and #5129 closed in favour of this PR. What still applies from their reviews:
6.0 keeps the app-wide stack and |
6 of 46 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Continues #5127 (originally by @konstmar, fork branch
overlay-focus-primitive), rebased onto the currentmain.Most of that branch has since landed on
mainin #5124, #5125 and #5126:overlay→modalrename onPortalModalrendering its ownPortalModalrole and nameThis PR carries over only what
mainstill lacks, with the original authorship kept:useOverlayDismisskeeps a module-level stack of open overlays. Android back and web Escape close only the topmost one. A non-dismissable modal absorbs the press instead of letting the app exit or closing the modal underneath.Modalnow uses the hook in place of its inline back handler.useOverlayFocusmoves focus into the modal when it opens and hands it back when it closes.ModalandDialogacceptinitialFocusRefandrestoreFocusRef, so an app can name the element to focus on open and the control to return to on close.AccessibilityInfo.sendAccessibilityEventis mocked, and theforwardedRefprop is stripped from the Reanimated snapshot serializer. Printing it never terminates, because it walks the host instance. This removesforwardedReffrom theFABExtendedsnapshots.Merge conflicts were resolved in favour of
main's accessibility props. The fork'sTODO_REMOVEplaceholders for #5125 are not carried over. Refs useReact.ComponentRef<typeof View>, becauseViewis no longer an instance type in RN 0.88.No breaking changes: the new props are optional.
Test plan
Automated, all passing on this branch:
yarn typecheckyarn lintyarn test(56 suites, 743 tests)useOverlayDismissanduseOverlayFocusunit tests, plus newModaltests for topmost-only dismissal, a non-dismissable modal not exiting the app on back, and focus on open and closeManual, with the example app's Dialogs screen:
restoreFocusRefinitialFocusRefandrestoreFocusRefoverride the defaults onModalandDialog