useFocusTrap
Manual focus trap for modal surfaces (WCAG 2.4.3 / 4.1.2). While active it
moves focus into the container, wraps Tab/Shift+Tab at the edges, fires
onEscape on Escape, and returns focus to the previously-focused element
on release. Modal uses it; Sheet-like overlays should too.
The trap is deliberately manual (no injected guard nodes): it adds nothing to the DOM, so visual baselines and layout stay untouched.
When to use
| ✅ Use when… | 🚫 Avoid when… |
|---|---|
|
|
Signature
useFocusTrap(ref: React.RefObject<HTMLElement | null>, opt: {
active: boolean;
onEscape?: () => void;
});ref— the container to trap focus inside. Give ittabIndex={-1}so it can receive initial focus when it holds no interactive element.opt.active— engage/release. Gate it on the surface being mounted (e.g.isOpen && presencewhen paired withuseExitTransition).opt.onEscape— close callback; theEscapeevent is stopped so outer layers don't also react.
Example
function Dialog({ open, onClose, children }) {
const panelRef = useRef<HTMLDivElement>(null);
const { mounted, state, ref } = useExitTransition(open);
useFocusTrap(panelRef, { active: open && mounted, onEscape: onClose });
if (!mounted) return null;
return (
<div
ref={(node) => {
panelRef.current = node;
ref(node);
}}
role="dialog"
aria-modal="true"
tabIndex={-1}
data-state={state}
>
{children}
</div>
);
}See also
useExitTransition— pairs with the trap for animated open/close.Modal— the reference consumer.