Provide a general summary of the feature here
Allow customising or disabling focus restoration when a ModalOverlay closes, so consumers can control where focus lands when the original trigger element has been removed from the DOM.
Perhaps there are already patterns to handle this but I could not find any!
🤔 Expected Behavior?
When a ModalOverlay closes and the element that was focused before it opened is no longer in the DOM, there should be a way to specify a fallback focus target or opt out of automatic restoration. This would prevent focus from falling to document.body.
😯 Current Behavior
ModalOverlay uses useModalOverlay which sets up a FocusScope with restoreFocus hardcoded to true. When the modal closes, FocusScope tries to restore focus to the previously focused element. If that element has been removed from the DOM, focus falls to document.body.
There is no prop on ModalOverlay or Modal to disable or redirect this behaviour.
This causes a visible focus flicker when consumers try to redirect focus using deferred calls (setTimeout(0) or requestAnimationFrame), since there is always at least one frame where focus sits on body before the deferred call runs. For screen reader users, this also leads to a brief and interrupted announcement of the document.body element.
💁 Possible Solution
Some potential options:
- Expose restoreFocus as a prop on
ModalOverlay. Allowing false would let consumers opt out and manage focus themselves without racing against the default behaviour.
restoreFocusTo prop — an element id (or ref but preference for id) that overrides the default restoration target.
- Fallback behaviour — when the original trigger element is no longer in the DOM, restore focus to an element specified by id instead of letting it fall to body.
🔦 Context
We have a confirmation dialog(s) where the triggering button is replaced by a loading state after the user confirms. We need focus to move to a parent container that displays the loading status. Our workaround of using setTimeout(0) in the onClose callback works functionally, but causes a visible single-frame flicker as focus briefly sits at body between FocusScope restoration and our deferred focus call.
Workarounds tried:
setTimeout(0) in onClose — works but causes flicker
requestAnimationFrame — same flicker
useEffect cleanup watching the open prop — same timing issue
- Focusing before the dialog closes — blocked by the modal's focus trap
Related issues:
💻 Examples
// Option 1: disable restoreFocus
<ModalOverlay isOpen={open} restoreFocus={false}>
// Option 2: specify a fallback target by id
<ModalOverlay isOpen={open} restoreFocusTo="outline-panel">
// Option 3: callback for custom handling
<ModalOverlay isOpen={open} onRestoreFocus={(originalElement) => {
if (document.contains(originalElement)) {
originalElement.focus()
} else {
document.getElementById('other-element')?.focus()
}
}}>
🧢 Your Company/Team
Genio (note-taking and presentation applications)
🕷 Tracking Issue
No response
Provide a general summary of the feature here
Allow customising or disabling focus restoration when a
ModalOverlaycloses, so consumers can control where focus lands when the original trigger element has been removed from the DOM.Perhaps there are already patterns to handle this but I could not find any!
🤔 Expected Behavior?
When a
ModalOverlaycloses and the element that was focused before it opened is no longer in the DOM, there should be a way to specify a fallback focus target or opt out of automatic restoration. This would prevent focus from falling todocument.body.😯 Current Behavior
ModalOverlayusesuseModalOverlaywhich sets up aFocusScopewithrestoreFocushardcoded totrue. When the modal closes,FocusScopetries to restore focus to the previously focused element. If that element has been removed from the DOM, focus falls todocument.body.There is no prop on
ModalOverlayorModalto disable or redirect this behaviour.This causes a visible focus flicker when consumers try to redirect focus using deferred calls (
setTimeout(0)orrequestAnimationFrame), since there is always at least one frame where focus sits on body before the deferred call runs. For screen reader users, this also leads to a brief and interrupted announcement of thedocument.bodyelement.💁 Possible Solution
Some potential options:
ModalOverlay. Allowingfalsewould let consumers opt out and manage focus themselves without racing against the default behaviour.restoreFocusToprop — an element id (or ref but preference for id) that overrides the default restoration target.🔦 Context
We have a confirmation dialog(s) where the triggering button is replaced by a loading state after the user confirms. We need focus to move to a parent container that displays the loading status. Our workaround of using setTimeout(0) in the onClose callback works functionally, but causes a visible single-frame flicker as focus briefly sits at body between
FocusScoperestoration and our deferred focus call.Workarounds tried:
setTimeout(0)in onClose — works but causes flickerrequestAnimationFrame— same flickeruseEffectcleanup watching the open prop — same timing issueRelated issues:
💻 Examples
🧢 Your Company/Team
Genio (note-taking and presentation applications)
🕷 Tracking Issue
No response