Provides a React hook for managing keyboard focus within overlay UI surfaces (modals, sidebars, nav panels), handling initial focus, Tab cycling, Escape key handling, and guarded focus restoration on close. ## Key Components ### `useFocusTrap(containerRef, active, options)` The primary export. Attaches a container-scoped `keydown` listener when `active` is `true` and cleans up on deactivate. | Parameter | Type | Description | |---|---|---| | `containerRef` | `RefObject` | Ref to the overlay container element | | `active` | `boolean` | Enables/disables the trap | | `options` | `UseFocusTrapOptions` | Optional configuration | ### `UseFocusTrapOptions` | Option | Type | Default | Description | |---|---|---|---| | `onEscape` | `() => void` | — | Called on Escape keypress; passes through without `stopPropagation` | | `contain` | `boolean` | `true` | When `false`, disables Tab cycling (for non-modal surfaces like sliding sidebars) | ### `FOCUSABLE_SELECTOR` Internal CSS selector matching all interactive, non-disabled, visible elements eligible for Tab focus. ## Usage Example ```typescript import { useRef } from 'react' import { useFocusTrap } from './use-focus-trap' function MobileNavPanel({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { const panelRef = useRef(null) // Tab cycles within the panel; Escape triggers onClose useFocusTrap(panelRef, isOpen, { onEscape: onClose }) return ( // tabIndex={-1} is required — see hook CONTRACT
Home
) } ``` > **Contract:** The container element **must** have `tabIndex={-1}`. Without it, clicking non-focusable areas inside the surface moves focus to `body`, causing the trap to stop receiving keyboard events. ## Behavior Notes - **Stacking-safe:** Listener is container-scoped, so a modal opened above a sidebar will not leak its Escape event downward. - **Stable callback:** `onEscape` is stored in a `useRef` to prevent the effect from re-running on every inline function re-render. - **Guarded restore:** On deactivate, focus returns to the previously focused element only if focus has not moved to another intentional target (e.g. a dialog that opened during the overlay's lifetime). ## Source [`use-focus-trap.ts`](https://github.com/flamingo-stack/openframe-oss-lib/blob/main/use-focus-trap.ts)