@bento/focus-lock
v0.0.2
Published
Focus lock primitive for managing focus within a scope
Readme
FocusLock
The @bento/focus-lock package provides focus management for containing and
controlling keyboard focus within specific areas of your application. Built on
top of React ARIA's FocusScope, it ensures focus remains trapped within
designated boundaries, making it essential for modals, dialogs, drawers, select
popovers, and other overlay components.
Installation
npm install --save @bento/focus-lockProps
The following properties are available to be used on the FocusLock component:
| Prop | Type | Required | Description |
|------|------|----------|------------|
| contain | boolean | No | Whether to contain focus within the scope.
When true, focus will cycle between focusable elements within the scope. |
| restoreFocus | boolean | No | Whether to restore focus to the previously focused element when the focus scope unmounts. |
| autoFocus | boolean | No | Whether to automatically focus the first focusable element when the focus scope mounts. |
| children | ReactNode | No | The content to render inside the focus lock.
Can be a single element or multiple elements. |
| onFocusEnter | (e: FocusEvent<Element, Element>) => void | No | Callback fired when focus enters the scope |
| onFocusLeave | (e: FocusEvent<Element, Element>) => void | No | Callback fired when focus leaves the scope |
| className | string \| ((state: FocusLockState) => string) | No | Render prop for className |
| style | ((state: FocusLockState) => CSSProperties) \| CSSProperties | No | Render prop for style |
| slot | string | No | A named part of a component that can be customized. This is implemented by the consuming component.
The exposed slot names of a component are available in the components documentation. |
| slots | Record<string, object \| Function> | No | An object that contains the customizations for the slots.
The main way you interact with the slot system as a consumer. |
For all other properties specified on the FocusLock component, they will be
passed down to the underlying React ARIA FocusScope component.
Examples
The simplest use case is to wrap your modal or dialog content with FocusLock
and enable focus containment.
Focus scopes can be nested, allowing you to have multiple layers of focus containment. When a nested scope is active, focus is trapped within the innermost scope.
The FocusLock component applies data attributes directly to its children
without introducing a wrapper element. This example demonstrates multiple
children (backdrop and content).
When used with a single child, the focus lock applies data attributes to that child element.
Focus lock is particularly useful for multi-step forms where you want to keep focus within the current step.
Customization
The FocusLock component is created using the @bento/slots package and allows
assignment of the custom slot property for overrides. The component applies
props to the underlying React ARIA FocusScope component and data attributes to
its children.
Slots
The @bento/focus-lock component is registered as BentoFocusLock and can be
customized using the slot system. See the @bento/slots package for more
information on how to use the slot and slots properties.
Render prop function receives a state object with the following properties:
interface FocusLockState {
hasFocus: boolean; // Whether focus is currently within the scope
isContained: boolean; // Whether focus is contained (same as contain prop)
}Data Attributes
The following data attributes are automatically applied to the children of the FocusLock component:
| Attribute | Description | Example Values |
| ---------------------- | ------------------------------------------------ | --------------- |
| data-focus-contained | Indicates whether focus is contained | "true" / "false"|
| data-has-focus | Indicates whether the scope currently has focus | "true" / "false"|
These data attributes can be targeted using CSS selectors for styling. When using data attributes for styling, ensure you scope them properly with a className to avoid affecting unrelated elements:
.my-modal[data-focus-contained="true"] {
outline: 2px solid blue;
}
.my-modal[data-has-focus="true"] {
background-color: rgba(0, 0, 0, 0.05);
}Apply the scoping className to your FocusLock children:
<FocusLock contain restoreFocus autoFocus>
<div className="my-modal">
Modal content
</div>
</FocusLock>Accessibility
Focus management is crucial for accessibility. The FocusLock component ensures
that keyboard users can navigate within the focus scope using Tab and Shift+Tab,
focus is trapped within the scope when contain is enabled, focus is
automatically restored to the previously focused element when the scope is
removed (when restoreFocus is enabled), and the first focusable element is
automatically focused when the scope is mounted (when autoFocus is enabled).
When using focus lock, follow these accessibility guidelines:
- Always provide a way to exit the focus scope (e.g., a close button or escape key handler)
- Use
restoreFocusto ensure users return to their previous location when the scope is closed - Use
autoFocusto immediately draw attention to important content like modals - Consider using
aria-modalon modal dialogs to provide additional context to screen readers - Ensure all focusable elements within the scope are keyboard accessible
