Library / SDK
focus-trap/focus-trap-react avatar
focus-trap/focus-trap-react

focus-trap-react: keeping keyboard focus inside a modal

A React component that traps focus

785 stars66 forksJavaScriptMIT

At a glance

What is it?
focus-trap-react is a thin React wrapper around the focus-trap library, and its job is to keep tab focus inside a container so a modal dialog does not leak focus to the page behind it. The interesting part is the React 19 story, because the component dropped propTypes and defaultProps and the Strict Mode remount behaviour forced a change in how activation is handled.
Who is it for?
Adopt focus-trap-react if you build modals or dialogs in React and need focus to stay inside them, and you are on React 18 or 19. Do not adopt it for a non-modal case, since a focus trap is deliberately hostile to the rest of the page, and be aware that the component is deliberately thin, so the actual behaviour, including what happens on activation and deactivation, is documented in the focus-trap project rather than here.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A wrapper, deliberately, with the real docs one click away

focus-trap-react is a React component that traps focus, and it describes itself as a light wrapper around focus-trap, tailored to React-specific needs. The design decision is to stay out of the way. This module simply provides a React component that creates and manages a focus trap, and the README tells you to read the focus-trap documentation to understand what a focus trap is, what happens when a focus trap is activated, and what happens when one is deactivated. That is the right division, because the hard part of focus trapping is the tab order semantics and the details of pausing and restoring focus, and reimplementing that in a React wrapper would mean maintaining a second implementation of the same rules. The stated use case is building an accessible modal, with react-aria-modal named as the kind of thing you might want it for, and there is a demo page linked from the README.

Activation is automatic by default, and can be driven by props

The component exposes three lifecycle behaviours, and the first is the one that catches people. The focus trap automatically activates when mounted, by default, though this can be changed, and it automatically deactivates when unmounted. Third, the focus trap can be activated and deactivated, paused and unpaused via props. That third one is the important one for a modal, because a modal usually has a visible cancel button that should close the dialog, and that means the trap has to be torn down on demand rather than only at unmount. The demo in the README shows exactly that pattern, with a mountTrap and an unmountTrap handler and the trap rendered conditionally so it disappears from the tree when deactivated.

Exactly one child, and never a Fragment

The usage section states the constraint plainly: you wrap any element that you want to act as a focus trap with the FocusTrap component, and FocusTrap expects exactly one child element which can be any HTML element or other React component that contains focusable elements. It cannot be a Fragment, because FocusTrap needs to be able to get a reference to the underlying HTML element, and Fragments do not have any representation in the DOM. The minimal example is two buttons inside a div:

js
<FocusTrap>
  <div id="modal-dialog" className="modal" >
    <button>Ok</button>
    <button>Cancel</button>
  </div>
</FocusTrap>

and a component version works the same way:

js
<FocusTrap>
  <ModalDialog okButtonText="Ok" cancelButtonText="Cancel" />
</FocusTrap>

The single-child rule is the constraint that will bite you in a real codebase, because a modal that renders a header, a body and a footer usually wants a Fragment around them. You will need a real element to hold the reference.

React 19 dropped propTypes, and the component changed with it

The React dependency section is a short history of a breaking change. React >= 18.0.0 is required. While React 18.x still supported propTypes and defaultProps, they had long since been deprecated and are completely dropped in React 19, so this library no longer assigns these properties to the FocusTrap element for runtime validation and initialization. The README recommends the techniques you would now use in React 19, which are backward compatible with React 18: use TypeScript for static prop type validation, or use a runtime validation library such as RTV.js, JSON Schema or yup to replace prop-types. The types are shipped, since the package manifest points its types field at index.d.ts at the root and includes it in the published files, so a TypeScript project gets the prop types without extra work. The support policy is stated too: the library aims to support one major version of React behind the current major version, since React major releases are typically years apart, to the extent that the feature drift is not too great.

Strict Mode remounting forced a reconciliation change in v9.0.2

React 18 introduced new behaviour in Strict Mode where it mimics a possible future behaviour where React might optimise an app's performance by unmounting certain components that are not in use and later remounting them with previous, reused state. Remounted with reused state is the key difference from what is otherwise expected about unmounted components, and what constitutes not in use and needs them again is as yet undefined. Version 9.0.2 added support for this behaviour: the trap attempts to detect that it has been remounted with previous state, and if the active prop's value is true and an internal focus trap instance already exists, the focus trap is re-activated on remount in order to reconcile stated expectations. The warning that follows says the trap will be deactivated as soon as the Strict Mode remount happens, and the note explains that this behaviour only affects dev builds, since that is the only place Strict Mode's double-invocation exists. The README also points to the focus-trap documentation for the details of the activation and deactivation semantics that this reconciling depends on.

Desktop browsers, and the performance caveat that comes with them

The browser support section is short and has a useful admission in it. The project is focused on desktop browsers, particularly Chrome, Edge, FireFox, Safari and Opera, and it is gated by what React supports in the version currently supported. Focus-trap-react is not officially tested on any mobile browsers or devices, which matters because the topic list includes tappable and react-a11y. Internet Explorer is no longer supported by the library, since Microsoft no longer supports any version of it. And the README notes that focus-trap-react relies on focus-trap, so its browser support is at least what focus-trap supports. The candid caveat is that performance optimization and old browser support are often at odds, so tabbable may not always be able to use the most optimal, typically modern, APIs in all cases. The package has a CI workflow, a codecov badge, a Cypress end-to-end setup with a cypress.config.js, Jest for unit tests, and a demo directory with its own HTML, JavaScript and CSS.

Installing it and driving the trap from state

The package installs from npm:

bash
npm install focus-trap-react

and dist/focus-trap-react.js is the Babel-compiled file that you will use. The README's fuller example shows a class component that holds an activeTrap boolean, renders the FocusTrap only when that is true, and passes a focusTrapOptions object with an onDeactivate callback so that the trap can tell the app it has been released:

jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { FocusTrap } from 'focus-trap-react';

That is the whole integration surface: the component, a boolean, and a callback. The licence is MIT with the LICENSE file at the root, the repository is not archived, the last push was on 2026-10-14, and the releases have been steady through the 12.x line, with v12.0.3 on 2026-06-22 after v12.0.2 in May and v12.0.1 in April. Releases are cut with changeset, since the repository has a .changeset directory and the release script runs changeset publish.

Editorial conclusion

Adopt focus-trap-react if you build modals or dialogs in React and need focus to stay inside them, and you are on React 18 or 19. Do not adopt it for a non-modal case, since a focus trap is deliberately hostile to the rest of the page, and be aware that the component is deliberately thin, so the actual behaviour, including what happens on activation and deactivation, is documented in the focus-trap project rather than here. Before you commit, read the focus-trap documentation as the README directs, decide whether you want automatic activation or prop-driven control, and add TypeScript or a runtime validation library if you were relying on propTypes for validation.

Frequently asked questions

What is focus-trap-react?

It is an MIT licensed React component that traps focus, described as a light wrapper around the focus-trap library tailored to React-specific needs. The trap activates automatically when mounted by default, deactivates when unmounted, and can also be activated, deactivated, paused and unpaused via props.

Can I use a Fragment as the child of FocusTrap?

No. FocusTrap expects exactly one child element, which can be any HTML element or other React component that contains focusable elements, and it cannot be a Fragment because FocusTrap needs a reference to the underlying HTML element and Fragments have no representation in the DOM.

Does focus-trap-react work with React 19?

Yes, with a caveat. React >= 18.0.0 is required, and since React 19 dropped propTypes and defaultProps, this library no longer assigns those properties for runtime validation and initialization. The README recommends TypeScript or a runtime validation library such as RTV.js, JSON Schema or yup instead.

What happens to the focus trap in React 18 Strict Mode?

React 18 Strict Mode mimics a possible future behaviour where components are unmounted and later remounted with previous, reused state. Version 9.0.2 added support for that: if the active prop is true and an internal focus trap instance already exists, the trap is re-activated on remount to reconcile expectations. This only affects dev builds.

Official sources

  1. focus-trap/focus-trap-react on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/focus-trap-focus-trap-react.svg)](https://hysenlabs.com/projects/focus-trap-focus-trap-react)

Was this useful?