# focus-trap: A Small, Dependency-Conscious Way to Keep Tab and Click Inside a Modal

> focus-trap is a vanilla JavaScript library that confines keyboard focus and outside clicks to a DOM node. It solves the core accessibility problem of modal dialogs without forcing a framework on you.

**focus-trap/focus-trap** — Trap focus within a DOM node.

- Repository: https://github.com/focus-trap/focus-trap
- Website: http://focus-trap.github.io/focus-trap/
- Stars: 1,564 · Forks: 125
- Language: JavaScript
- License: MIT
- Published: 2026-08-21 · Updated: 2026-08-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/focus-trap-focus-trap

## The Problem: Escaping Focus Breaks Accessible Modals

When a modal opens, a keyboard user should not be able to Tab into the page behind it. The same applies to a custom dropdown or a side drawer. Without some containment, focus wanders off into the background, and screen reader users lose track of where they are. focus-trap exists to solve exactly this: it keeps Tab, Shift+Tab, and clicks inside a specified DOM node until you explicitly release it. The intended audience is developers building accessible modals or similar overlay components, particularly those working in vanilla JavaScript or in a framework that has no built-in trap. The README points React users to focus-trap-react, a wrapper, which tells you the library is a low-level building block rather than a full modal solution.

## What the Trap Actually Does on Activation and Deactivation

The behavior is spelled out in the README. On activation, focus moves to the first tabbable element in the trap's tab order, as determined by the tabbable library, unless you specify an initial element. Tab and Shift+Tab cycle through the trap's tabbable elements and never leave the trap. Clicks inside work normally, but clicks outside are blocked. Escape deactivates the trap. On deactivation, focus returns to the element that had focus when the trap was activated, typically the button that opened the modal. There is also a pause mechanism for nested traps, such as a modal inside another modal, which lets you suspend one trap without tearing it down. The API exposes lifecycle callbacks like onActivate, onPostActivate, onPause, and onUnpause, giving you hooks to coordinate with your own UI logic.

## Installation and the UMD Caveat

Installation is a single command: npm install focus-trap. The package ships as ESM, CommonJS, and UMD. The UMD build is available on unpkg as dist/focus-trap.umd.js and dist/focus-trap.umd.min.js. The README includes a warning: the UMD build does not bundle the tabbable dependency. You must include tabbable first, then focus-trap, in that order. The example shows two script tags, one for tabbable's UMD build and one for focus-trap's. This is a small but real integration detail. If you are using a bundler, you likely do not care, but for a quick script-tag prototype, forgetting the order will break the trap silently.

## Configuration Options: Lifecycle Callbacks and Timing

The createFocusTrap(element, options) function accepts a DOM node, a selector string, or an array of both. The order of an array determines where focus goes after the last tabbable element of one container. The options include onActivate, onPostActivate, onPause, onPostPause, onUnpause, and onPostUnpause. Each receives a LifecycleParameters object. The README notes that if delayInitialFocus is true, the default, onPostActivate is called after focus is sent, but onPostUnpause is called before focus is re-set. There is also checkCanFocusTrap, which expects a promise. This is for animated dialogs: the trap may not be focusable immediately after onActivate, so you can delay focus until the animation finishes. This is a thoughtful touch, but it means you must write the promise logic yourself. The library does not animate anything for you.

## The Fallback Focus Requirement and Its Failure Mode

A focus trap must have at least one container with at least one tabbable or focusable node. The trap can adjust when nodes are added or removed at runtime, but the README is explicit: an error is thrown if the trap ever reaches a state where none of its containers have tabbable nodes and the fallbackFocus option does not resolve to an alternate node. This is a real constraint. If your modal content is empty or all its focusable elements are conditionally hidden, the trap will throw. You must either ensure content is always present or provide fallbackFocus, which can be a selector or element. This is a sharp edge that catches developers who assume a trap on an empty container is harmless. It is not.

## Browser Support and the Safari Caveat

The README states browser support is as broad as reasonably possible, excluding out-of-support browsers. It focuses on desktop Chrome, Edge, Firefox, Safari, and Opera. Mobile browsers are not officially tested. There is a specific warning about Safari: by default, Safari does not tab through all elements, which changes the DOM-based tab order that focus-trap expects. Users must enable Preferences > Advanced > Press Tab to highlight each item on a webpage, otherwise the trap will not work as expected. This is a significant limitation for public-facing sites where you cannot control user settings. IE is no longer supported because Microsoft retired it. The library relies on tabbable, so its browser support is at least what tabbable supports, but the README notes that performance optimization and old browser support are often at odds, so tabbable may not always use the most optimal APIs.

## Alternatives and the Trade-Off in Approach

The main alternative is the native HTML dialog element with the showModal() method. The browser handles focus trapping and the inert background for you, with no JavaScript library. The difference in approach is structural: native dialog gives you a built-in top layer, focus containment, and Escape handling, but it comes with browser quirks and limited styling control in some browsers. focus-trap gives you full control over the DOM and styling, works with any element you designate as a container, and integrates with custom animations via checkCanFocusTrap. The trade-off is that you must manage the trap lifecycle yourself, including activation and deactivation, and you inherit the Safari tabbing caveat. If you need a non-modal overlay or a custom widget that is not a dialog, native dialog will not help, and focus-trap is the more flexible choice.

## Maintenance, License, and Upgrade Considerations

The repository is actively maintained, with a recent release in June 2026. It is licensed under MIT, which means you can use it in commercial projects without copyleft obligations, but you should keep the license notice in your distribution as MIT requires. The dependency on tabbable is a maintenance cost: when tabbable changes its behavior or browser support, focus-trap inherits that. Upgrading focus-trap may require checking the changelog for API changes, especially around the lifecycle callbacks and the checkCanFocusTrap promise semantics. The README does not document a migration guide, so you should read the release notes for each major version. The library is small and focused, so the upgrade surface is limited, but it is not zero.

## Conclusion

Adopt focus-trap if you are building accessible modals, menus, or any component that must keep keyboard focus and outside clicks contained, and you want a framework-agnostic, MIT-licensed solution. Avoid it if you need mobile browser support (it is not officially tested there) or if you cannot rely on users enabling Safari's full keyboard access setting. Before committing, verify that your target browsers are covered by tabbable's support matrix and that you understand the fallbackFocus requirement, because a trap with no tabbable nodes will throw an error unless you provide a fallback. In short, focus-trap is a precise, small tool that does one thing well, but it assumes a desktop, keyboard-tabbing environment and leaves the UI animation timing to you.

## FAQ

### What is a focus trap?

A focus trap keeps keyboard focus inside a chosen DOM node so that pressing Tab, Shift+Tab or clicking around cannot escape a cycle of focusable elements. The focus-trap module implements that as a small vanilla JS piece you use inside higher-level components, mainly so accessible modals keep focus where it belongs.

### what is focus trap react

The focus-trap README points React users at focus-trap-react, described as a light wrapper around this library, and tells everyone else to consider creating light wrappers in their own framework of choice. The library itself is framework-agnostic and expects you to wrap it.

### Does focus-trap work in Safari without any user setup?

Not by default. Safari does not tab through all elements on a page, so users must enable Preferences > Advanced > Press Tab to highlight each item on a webpage. Without that setting the README says traps will not work the way you expect, and it links issue 783.

### Do I have to install tabbable separately for the focus-trap UMD build?

Yes. The UMD build does not bundle the tabbable dependency, and it has to be loaded before focus-trap. The README shows both script tags in one head block, tabbable first, using the unpkg builds of each.

### When does focus-trap throw an error?

Only when two things are true at once: none of the trap's containers contain any tabbable nodes, and the fallbackFocus option does not resolve to an alternate node where focus can go. Nodes added or removed at runtime are handled, since the trap adjusts to the change.

### Where does focus-trap put focus when the trap is deactivated?

Back on whichever element had focus when the trap was activated, such as the button that opened the modal. The Escape key deactivates the trap, and once deactivated, tabbing and clicking behave normally everywhere again.

## Sources

- [Official documentation](http://focus-trap.github.io/focus-trap/)
- [Official README](https://github.com/focus-trap/focus-trap#readme)
- [Project repository](https://github.com/focus-trap/focus-trap)
- [Release notes](https://github.com/focus-trap/focus-trap/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/focus-trap-focus-trap
