Focus trap
The FocusTrap component prevents the user's focus from escaping its children components.
Introduction
FocusTrap
is a utility component that is useful when implementing an overlay such as a modal dialog, which should block all interactions outside of it while open.
Component
Usage
After installation, you can start building with this component using the following basic elements:
import FocusTrap from '@mui/base/FocusTrap';
export default function MyApp() {
return <FocusTrap>{/* children where the focus will be trapped */}</FocusTrap>;
}
Basics
FocusTrap
wraps around the UI elements that should hold the user's focus. For instance, if the focus needs to stay inside of a MenuUnstyled
, then the component will be structured like this:
<FocusTrap>
<MenuUnstyled>
<MenuItemUnstyled>{/* item one */}</MenuItemUnstyled>
<MenuItemUnstyled>{/* item two */}</MenuItemUnstyled>
</MenuUnstyled>
</FocusTrap>
The following demo shows a <button>
that opens a Box
component nested inside of a FocusTrap
. As long as the Box
is open, the user's keyboard cannot interact with the rest of the app. Press the Open button and then use the Tab key to move the focus—notice that it will not leave the Box
:
<button type="button" onClick={() => setOpen(true)}>
Open
</button>
{open && (
<FocusTrap open>
<Box tabIndex={-1} sx={{ mt: 1, p: 1 }}>
<label>
First name: <input type="text" />
</label>
<br />
<button type="button" onClick={() => setOpen(false)}>
Close
</button>
</Box>
</FocusTrap>
)}
Customization
Disable enforced focus
By default, clicks outside of the FocusTrap
component are blocked.
You can disable this behavior with the disableEnforceFocus
prop.
Compare the following demo with the demo from the Basics section—notice how that demo prevents you from clicking outside of it, while this one allows it:
<button type="button" onClick={() => setOpen(true)}>
Open
</button>
{open && (
<FocusTrap disableEnforceFocus open>
<Box tabIndex={-1} sx={{ mt: 1, p: 1 }}>
<label>
First name: <input type="text" />
</label>
<br />
<button type="button" onClick={() => setOpen(false)}>
Close
</button>
</Box>
</FocusTrap>
)}
Lazy activation
By default, the FocusTrap
component automatically moves the focus to the first of its children when the open
prop is present.
You can disable this behavior and make it lazy with the disableAutoFocus
prop. When auto focus is disabled—as in the demo below—the component only traps the focus once the user moves it there:
<button type="button" onClick={() => setOpen(true)}>
Open
</button>
{open && (
<FocusTrap open disableAutoFocus>
<Box tabIndex={-1} sx={{ mt: 1, p: 1 }}>
<label>
First name: <input type="text" />
</label>
<br />
<button type="button" onClick={() => setOpen(false)}>
Close
</button>
</Box>
</FocusTrap>
)}
Escape the focus loop
The following demo uses the Portal
component to render a subset of the FocusTrap
children into a new "subtree" outside of the current DOM hierarchy, so they are no longer part of the focus loop:
Using a toggle inside the trap
The most common use case for the FocusTrap
component is to maintain focus within a modal component that is entirely separate from the element that opens the modal. But you can also create a toggle button for the open
prop of the FocusTrap
component that is stored inside of the component itself, as shown in the following demo:
<React.Fragment>
<FocusTrap open={open} disableRestoreFocus disableAutoFocus>
<Stack alignItems="center" spacing={2}>
<button type="button" onClick={() => setOpen(!open)}>
{open ? 'Close' : 'Open'}
</button>
{open && (
<label>
First name: <input type="text" />
</label>
)}
</Stack>
</FocusTrap>
</React.Fragment>