React Focus Trap Component
focus-trap-react is a React component that wraps the `focus-trap` library, providing an accessible way to trap focus within a DOM element. This is crucial for UI patterns like modals, dialogs, and overlays, ensuring keyboard users cannot tab outside the active component. The current stable version is 12.0.0. The library maintains a steady release cadence, typically releasing new patch versions to update its underlying `focus-trap` dependency, and major versions coinciding with significant updates to `focus-trap` or React compatibility requirements. Its key differentiator is its direct integration with React's component lifecycle, abstracting the imperative `focus-trap` API into declarative props, simplifying its use in React applications for enhanced accessibility.
Common errors
-
Error: FocusTrap requires a single child element.
cause You are passing more than one child or a React Fragment to the FocusTrap component.fixWrap your children in a single parent DOM element, like a `<div>`, before passing them to FocusTrap. Example: `<FocusTrap><div>...</div></FocusTrap>` -
TypeError: Cannot read properties of undefined (reading 'displayName')
cause This error can occur in older React versions or specific build configurations when using the named `FocusTrap` export if a default export was expected or vice versa, or if `React.Children.only` fails.fixEnsure you are using `import { FocusTrap } from 'focus-trap-react';` and that your React version is `>=18.0.0`. Also, verify FocusTrap has exactly one child element. -
Property 'onPostActivate' does not exist on type 'FocusTrapOptions'.
cause TypeScript error indicating that the `onPostActivate` option might be used incorrectly or its signature has changed, especially after the v12 update to `focus-trap` v8.fixCheck the `focus-trap` v8 documentation for `onPostActivate` usage. Ensure your `focusTrapOptions` object conforms to the expected type, and review the behavioral change regarding its invocation timing.
Warnings
- breaking The underlying `focus-trap` dependency was updated to v8.0.0. The `onPostActivate()` callback is now correctly called *after* the initial focus node is focused, rather than before, which was a bug.
- breaking Support for `propTypes` and `defaultProps` has been removed. This aligns with React 19's deprecation of these features and forward compatibility with React 18, which already deprecated them.
- gotcha The default export for `FocusTrap` is deprecated. While still available, it is recommended to use the named export for future compatibility.
- gotcha The `FocusTrap` component requires exactly one child element. It cannot be a React Fragment (`<>...</>`) because the component needs a direct reference to a DOM element.
- gotcha This library only officially supports desktop browsers. While it may function on mobile, it is not officially tested or guaranteed.
- gotcha Internet Explorer is no longer supported by this library, following Microsoft's discontinuation of support for IE 11.
Install
-
npm install focus-trap-react -
yarn add focus-trap-react -
pnpm add focus-trap-react
Imports
- FocusTrap
const FocusTrap = require('focus-trap-react').FocusTrap;import { FocusTrap } from 'focus-trap-react'; - FocusTrap (default export, deprecated)
const FocusTrap = require('focus-trap-react');import FocusTrap from 'focus-trap-react';
- FocusTrapProps (TypeScript types)
import type { FocusTrapProps } from 'focus-trap-react';
Quickstart
import React, { useState, useRef, useEffect } from 'react';
import { FocusTrap } from 'focus-trap-react';
const Modal = ({ onClose }) => {
const trapRef = useRef(null);
useEffect(() => {
// Optional: Log when the trap is activated/deactivated
console.log('Focus trap mounted');
return () => console.log('Focus trap unmounted');
}, []);
return (
<FocusTrap
active={true}
focusTrapOptions={{
onDeactivate: onClose,
clickOutsideDeactivates: true,
initialFocus: '#close-button',
fallbackFocus: '#close-button'
}}
_ref={trapRef}
>
<div
style={{
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: 'white',
padding: '20px',
border: '1px solid gray',
zIndex: 1000
}}
aria-modal="true"
role="dialog"
>
<h2>Accessible Modal Dialog</h2>
<p>This is content inside the focus trap. You cannot tab outside this box.</p>
<input type="text" placeholder="Enter something" />
<button id="close-button" onClick={onClose}>Close Modal</button>
<button>Another button</button>
</div>
</FocusTrap>
);
};
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div>
<h1>Focus Trap React Demo</h1>
<button onClick={() => setIsModalOpen(true)}>Open Modal</button>
{isModalOpen && <Modal onClose={() => setIsModalOpen(false)} />}
<p>Content behind the modal.</p>
<input type="text" placeholder="Outside input" />
</div>
);
};
export default App;