Introducing PrimeReact v11-alpha 🎉Discover Now

FocusTrap

Headless hook for trapping keyboard focus within a container element.

Register
basic-demo

Usage#

import { useFocusTrap } from '@primereact/headless/focustrap';
const { containerRef, firstHiddenElementRef, lastHiddenElementRef, firstHiddenProps, lastHiddenProps } = useFocusTrap();
 
<>
    <span ref={firstHiddenElementRef} tabIndex={0} {...firstHiddenProps} className="sr-only" />
    <div ref={containerRef}></div>
    <span ref={lastHiddenElementRef} tabIndex={0} {...lastHiddenProps} className="sr-only" />
</>;

useFocusTrap keeps focus inside a container while it is active, managing initial focus, tab cycling, and escape dismissal. See Primitive for a component-based API.

Features#

  • Boundary wiring — containerRef plus firstHiddenElementRef / lastHiddenElementRef define the trap edges with invisible sentinel spans
  • Auto focus strategy — on mount, focus prioritizes initialFocusRef, then [autofocus] / [data-autofocus], then the first focusable element
  • Tab cycling — focus wraps from the last element back to the first and vice versa, with override hooks for both edges
  • Dynamic content — a MutationObserver refocuses when children are added or removed from the container
  • Escape handling — onEscape fires when the user presses Escape inside the trap so you can close the surrounding UI

Working with callbacks#

Direct initial focus#

Pass initialFocusRef to land focus on a specific element, which takes priority over any autoFocus attributes.

const inputRef = React.useRef<HTMLInputElement>(null);
const focustrap = useFocusTrap({ initialFocusRef: inputRef });

Close on Escape#

Wire onEscape to dismiss the surrounding dialog or menu when the user presses Escape.

const focustrap = useFocusTrap({
    onEscape: () => setOpen(false)
});

Override tab wrap-around#

Replace the default wrap behavior with onTabFirst and onTabLast when you need custom focus flow — for example, moving focus to an external element when the user tabs past the last trapped element.

const focustrap = useFocusTrap({
    onTabFirst: (e) => focusPreviousPanel(),
    onTabLast: (e) => focusNextPanel()
});

Temporarily disable the trap#

Toggle trapped or autoFocus when the trap should be mounted but inactive — for example, while an inner modal owns focus.

const focustrap = useFocusTrap({ trapped: isActive, autoFocus: isActive });

Styling with data attributes#

The container receives a data-focus-trap attribute when the trap is active, giving you a hook for debugging or visual outlines.

[data-focus-trap] {
    outline: 2px solid var(--p-primary-color);
}

API#

useFocusTrap#

NameTypeDefault
trappedbooleantrue
When enabled, focus is trapped within the container element.
autoFocusbooleantrue
When enabled, the first focusable element receives focus on mount.
initialFocusRefRefObject<HTMLElement>—
Reference to the element that should receive focus when the trap activates. Takes priority over autoFocus and [autofocus] attribute detection.
onEscape(event: KeyboardEvent) => void—
Callback invoked when the Escape key is pressed inside the trap.
onTabFirst(event: FocusEvent<HTMLElement>) => void—
Callback invoked when Shift+Tab is pressed before the first focusable element. When provided, overrides the default cycling behavior.
onTabLast(event: FocusEvent<HTMLElement>) => void—
Callback invoked when Tab is pressed past the last focusable element. When provided, overrides the default cycling behavior.

Accessibility#

Tab cycles focusable elements inside, Shift+Tab reverses, and focus returns to the trigger when unmounted. See Primitive for full WAI-ARIA compliance details.