Skip to content

useFocusRestore

standalonefocusaccessibilityoverlay

Capture and restore focus around overlays, panels, command palettes, and triggerless temporary UI with nested stack safety.

useFocusRestore captures the currently focused element before temporary UI opens and returns focus to it when that UI closes. It is standalone (no provider) and stack-aware, so nested overlays restore in close order and an older layer never steals focus from a newer one. Use it when there is no single trigger button to focus back — command palettes, globally summoned panels, and toasts-turned-dialogs.

tsx
const focusRestore = useFocusRestore()

When to use it

  • A panel or palette is opened from a keyboard shortcut rather than a specific button, so focus must return to wherever the user was.
  • Several temporary surfaces can stack and each must restore focus to the layer beneath it.
  • You need restore semantics but not Tab trapping — for trapping, use useFocusTrap (which manages restoration internally).

Installation

$pnpm exec dgadd add keys/focus-restore
[Installs to]src/hooks/use-focus-restore.ts[Item]keys/focus-restore

dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.

@diffgazer/keys requires no CSS or Tailwind setup.

Parameters

NameTypeDefaultDescription
options.enabledbooleantrueWhether capture and restore are active.
options.restoreOnUnmountbooleantrueRestore focus during cleanup if capture was called and restore was not called manually.
options.preventScrollbooleanfalsePass preventScroll to the focus call when restoring focus.
options.fallbackHTMLElement | nullnullFallback element to focus when the captured target is unavailable.

Returns

{ capture: (ownerDocument?: Document) => HTMLElement | null; restore: () => boolean; target: HTMLElement | null }capture stores the current focus target, restore focuses the captured or fallback target, and target exposes the last captured element.

Call capture() as the overlay opens (storing the active element) and restore() as it closes. target exposes the last captured element.

Examples

Temporary panel focus restore

Preview

Triggerless palette with fallback

Preview

Notes

Hook boundary

useFocusRestore is the React hook. getRestorableFocusTarget and restoreFocus are plain DOM utilities for non-hook code.

Nested overlays

Focus restore entries are stack-aware, so nested overlays restore focus in close order and older layers do not steal focus from newer ones.

Edge cases

  • fallback. When nothing was focused at capture time, or the captured element is gone, focus moves to the fallback element. Supply one for triggerless UI so focus never lands on <body>.
  • restoreOnUnmount. On by default — if the component unmounts after capture() without an explicit restore(), cleanup restores focus. Set it to false when another hook (such as useFocusTrap) owns the restoration timing.
  • Stack ordering. Only the top stack entry restores. Releasing a middle entry removes it without moving focus, so nested overlays unwind correctly.
  • Hook vs utilities. useFocusRestore is the React hook; getRestorableFocusTarget and restoreFocus are plain DOM utilities for non-hook code.

Source

Highlighted source loads after this disclosure opens. Browse the source repository.