Intentional Focus Trap

An intentional focus trap keeps keyboard focus inside an active modal or similar overlay while it is open, so users do not accidentally reach obscured page content. It should also provide a predictable way to close the overlay and restore focus to the element that opened it.

A modal changes the user's interaction context: content behind it is typically unavailable until the modal closes. Keyboard focus should therefore remain within the modal while it is active. This prevents Tab navigation from moving into controls that the user cannot meaningfully interact with.

A basic containment pattern identifies the focusable elements inside the modal and handles forward and backward Tab navigation at the boundaries. When focus is on the last focusable element, Tab can move it to the first; when focus is on the first, Shift+Tab can move it to the last.

When the modal opens, focus should move to an appropriate element inside it, such as the dialog's heading, close button, or first meaningful control. When it closes, focus should normally return to the control that initiated the modal, provided that control still exists and is usable.

A focus trap is not appropriate for every overlay. Non-modal popovers, menus, and other patterns have different keyboard interaction requirements. For a modal dialog, the implementation should also expose the correct semantics, such as `role="dialog"` and an accessible name, and the rest of the document should be prevented from being interactable while the modal is active.

A robust implementation must account for dynamically changing focusable elements, disabled controls, nested dialogs, and cleanup when the modal closes. In production code, using a well-tested dialog or accessibility primitive can reduce the chance of missing these edge cases.

Key points

Interview questions

When should you avoid a focus trap?

I would not automatically trap focus for every overlay component. A non-modal popover, tooltip, or menu has different semantics and keyboard expectations because the surrounding page may remain available. Applying modal behavior to those components can interfere with normal navigation. The component's intended interaction model should determine whether focus containment is appropriate.

Why would you trap keyboard focus inside a modal?

A modal represents a temporary interaction context, so controls behind it should not receive keyboard focus while it is active. Without containment, a keyboard user can tab into page content that is visually obscured or otherwise unavailable. Keeping focus inside the modal makes the interaction predictable. The modal should also provide a clear way to close and return focus to the initiating control.

How would you implement a basic focus trap?

I would collect the currently focusable elements inside the modal and listen for Tab key events. If focus is on the last element and the user presses Tab, I would move focus to the first; for Shift+Tab on the first element, I would move focus to the last. I would move focus into the modal when it opens and restore it to the opener when the modal closes. I would also make sure the handler is removed or disabled when the modal is no longer active.

What else is required for an accessible modal besides trapping focus?

The modal needs appropriate dialog semantics and an accessible name, and users need a reliable way to dismiss it when dismissal is supported by the interaction. The content outside the modal should not remain interactable while the modal is active. Focus should enter the dialog at an intentional location and normally return to the invoking element afterward. Keyboard behavior such as Escape should be handled according to the dialog's intended interaction model.

What edge cases would you consider in a production focus-trap implementation?

I would consider dynamically added or removed controls, disabled elements, focusable elements with changing `tabindex` values, and a dialog whose opener is removed while it is open. I would also consider nested dialogs and ensure that only the active interaction context owns focus. The implementation should clean up listeners and state when unmounted or closed. For complex applications, I would strongly consider a well-tested dialog primitive rather than maintaining all of these rules manually.