Focus trap behavior

Used prevent focus from leaving a particular element.

On this page

On this page

The focusTrap behavior and useFocusTrap hook are used prevent focus from leaving a particular element. This is useful for implementing modal dialogs: only the content within the dialog should be interactive, even though the UI underneath may still be visible.

Behavior

  • Activation: As soon as the focus trap is activated, it will ensure that an element within the container has focus. If it doesn't, it will focus the first focusable element within the container, or, if provided, the element indicated by the initialFocus parameter (see API below).
  • External focus changes: If an external cause (e.g. mouse click, scripts, or accessibility software) results in an element outside the container to be focused, focus will immediately be redirected to the last-focused element that is inside the container.
  • Circular tab focus: Using the TAB key on the last focusable element within the container will result in the first focusable element within the container receiving focus. Similarly, Shift+TAB can be used to focus the last element from the first.
  • Global: Only one focus trap can be active at a time. When a focus trap is enabled, if there is already an active focus trap, it becomes suspended and pushed onto a stack. Once the newly-active focus trap is disabled, the most recently-suspended trap will reactivate. Suspended focus traps can be disabled, causing them to be removed from the stack of suspended traps.

Demo

To see a demo, deploy Storybook and find the useFocusTrap stories.

Usage

function showDialog() {
const dialog = document.getElementById('myDialog')
if (dialog instanceof HTMLElement) {
dialog.style.display = ''
return focusTrap(dialog)
}
}
function hideDialog(controller: AbortController) {
document.getElementById('myDialog')?.style.display = 'none'
controller.abort()
}
const dialogController = showDialog()
// later
if (dialogController) {
hideDialog(controller)
}

API

The focusTrap function takes the following arguments.

NameTypeDefaultDescription
containerHTMLElementWhen active, only elements within this container (along with the container itself) can be focused.
initialFocusHTMLElementSpecifies the element which will receive focus when the focus trap is activated. Defaults to the first tabbable element inside the container.
signalAbortSignal?undefinedOptional abort signal to control the focus trap. If one is not provided, an AbortController will be returned, which can be used to disable the focus trap.

Return value

If the signal argument is omitted, focusTrap() will return an AbortController. This object has an abort() method, which can be called to disable the focus trap.

Best practices

  • Focus management is an important consideration for accessible applications. Sometimes poor focus management can make certain tasks impossible to complete for users not using a mouse. To learn more, read the ARIA guidelines for keyboard focus.
  • Only activate a focus trap if all UI outside of the trap container should be inert (non-interactive).
  • Avoid situations where multiple focus traps may be active (e.g. dialogs that open more dialogs). This behavior handles those situations, but the pattern may indicate poor UX.

A note on performance

When focus trap is activated, it must perform reflow to discover focusable elements. Use caution not to rapidly enable and disable focus traps.

useFocusTrap hook

The useFocusTrap hook is used to achieve focus trapping for React components. The hook returns a ref that must be applied to the focus trap container. The hook also returns a ref that can be used to indicate the initially-focused element when a focus trap is activated.

The focus trap can be disabled in two ways:

  1. Simply do not render the component. When a component that uses focus trapping is unmounted, its focus trap will be aborted automatically.
  2. Pass disabled: true to the settings argument of useFocusTrap.

The useFocusTrap hook also has an additional setting, restoreFocusOnCleanUp. If this is set to true, when the hook is either disabled (called with disabled: true or unmounted), we will attempt to re-focus the element that was focused immediately before the focus trap was enabled.

Using your own refs

If you would like to use your own refs, you can pass them into the hook as part of the settings object (see the interface below).

useFocusTrap usage

export const FocusTrapExample = () => {
const {containerRef} = useFocusTrap()
return (
<div ref={containerRef as React.RefObject<HTMLDivElement>}>
<Button>Apple</Button>
<Button>Banana</Button>
<Button>Cantaloupe</Button>
</div>
)
}

FocusTrapHookSettings interface

FocusTrapHookSettings has the following properties:

NameTypeDefaultDescription
containerRefReact.RefObjectundefinedIf provided, this will be the ref used to access the focus trap container. Otherwise, this hook will create the ref for you and return it. In both cases, the ref must be provided to the container element's JSX.
initialFocusRefReact.RefObjectundefinedIf provided, this will be the ref used to access the element that should receive initial focus when the focus trap is activated. Otherwise, this hook will create the ref for you and return it. If unused, the first tabbable element inside the container will be focused.
disabledbooleanfalseIf true, the previously-established focus trap for this container will be aborted.
restoreFocusOnCleanUpbooleanfalseIf true, attempt to restore focus to the previously-focused element when the trap is disabled or unmounted.