OverlaysReact
Keeps keyboard focus inside a region while it is active, for an overlay or an editing area that is not a dialog.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<FocusTrap />Examples
Inline
An address edited in place with modal={false}: Tab wraps inside the form while the page stays usable, and Escape cancels.
Inside
A note panel whose formatting toolbar sits outside its element and joins the Tab cycle through inside.
Nested
A settings panel that opens a confirm panel inside it; Escape closes the inner one first, then the outer.
API reference
7 props across 1 parts, in React syntax. className, style, render are listed once, under Shared props.
FocusTrapFocusTrap
Keeps keyboard focus inside its region while active: Tab and Shift+Tab wrap at the edges, Escape asks to deactivate, and under modal the rest of the page is inert. Focus returns where it was on deactivation. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
activeWhether the trap is active. | boolean | — |
defaultActiveWhether the trap is initially active. | boolean | true |
onActiveChangeEvent handler called when the trap asks to deactivate: on Escape, with reason escape-key. | ((active: boolean, eventDetails: { reason: "escape-key"; event: KeyboardEvent; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element | undefined; }) => void) | — |
modalWhether the rest of the page is inert while the trap is active. | boolean | true |
insideElements that belong to the trap though they are outside its element (a toolbar button, a | FocusTrapInsideItem[] | (() => FocusTrapInsideItem[]) | — |
initialFocusDetermines the element to focus when the trap activates. | boolean | React.RefObject<HTMLElement | null> | ((interactionType: FocusTrapInteractionType) => boolean | void | HTMLElement | null) | true |
finalFocusDetermines the element to focus when the trap deactivates. | boolean | React.RefObject<HTMLElement | null> | ((interactionType: FocusTrapInteractionType) => boolean | void | HTMLElement | null) | true |
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
| Keys | Does | Part |
|---|---|---|
| Tab | On the last control, moves to the first | FocusTrap |
| ShiftTab | On the first control, moves to the last | FocusTrap |
| Tab | From the last control, moves to an inside element, and from it to the first | FocusTrap |
| TabShiftTab | With nothing tabbable inside, keeps focus on the region | FocusTrap |
| Escape | Calls onActiveChange with false and the reason escape-key | FocusTrap |
| Escape | Deactivates an uncontrolled trap and returns focus | FocusTrap |
| Escape | In a nested trap, deactivates only the inner one | FocusTrap |
| Escape | In a trap inside a dialog, deactivates the trap and leaves the dialog open | FocusTrap |
Accessibility
| Part | Sets | So that |
|---|---|---|
| FocusTrap | no role, no name, no aria-modal | the region is announced as what it holds; the app gives it a role and a name where it needs one |
| FocusTrap | tabindex="-1" and [data-active] while active | the region can hold focus itself when nothing inside is tabbable |
| FocusTrap | a visually hidden focus guard as its first and last child while active, with [data-base-ui-focus-guard] | Tab out of either edge, and a screen reader's virtual cursor, land back inside |
| FocusTrap | inert and [data-base-ui-inert] on the rest of the page while modal is on, live regions left out | the page behind cannot be focused, pressed or read, and announcements still reach the reader |
Focus order
- Activating moves focus to the first element with an
autofocusattribute, else the first tabbable element, else the region itself (the region after a touch), or toinitialFocus; focus already inside stays where it is. TabandShift+Tabmove through the region's controls, then theinsideelements in their order, and wrap at either end.- When the focused control is removed, hidden or disabled, focus moves to the region without scrolling.
- Deactivating returns focus to the element focused at activation, or to
finalFocus, without scrolling; undermodal={false}, focus the user moved outside stays there.