Top Related Projects
Trap focus within a DOM node.
🍿⚛Official React library to use Popper, the positioning library
Accessible modal dialog component for React
Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
Quick Overview
React Focus Lock is a React component that traps focus within a specified DOM node. It's designed to improve accessibility in modal dialogs, dropdown menus, and other interactive elements by preventing focus from leaving the designated area.
Pros
- Improves accessibility for keyboard users
- Easy to implement in React applications
- Supports both mouse and keyboard interactions
- Customizable with various props for different use cases
Cons
- May require additional setup for complex nested components
- Could potentially interfere with other focus management tools if not configured properly
- Limited to React applications (not usable in vanilla JavaScript or other frameworks)
- Might have a slight learning curve for developers unfamiliar with focus management concepts
Code Examples
- Basic usage with a modal:
import FocusLock from 'react-focus-lock';
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return (
<FocusLock>
<div className="modal">
{children}
<button onClick={onClose}>Close</button>
</div>
</FocusLock>
);
}
- Using with a disabled state:
import FocusLock from 'react-focus-lock';
function DisableableFocusLock({ children, isActive }) {
return (
<FocusLock disabled={!isActive}>
{children}
</FocusLock>
);
}
- Implementing with a ref:
import FocusLock from 'react-focus-lock';
import { useRef } from 'react';
function FocusableArea() {
const ref = useRef(null);
return (
<FocusLock>
<div ref={ref} tabIndex="-1">
<button>Button 1</button>
<button>Button 2</button>
</div>
</FocusLock>
);
}
Getting Started
-
Install the package:
npm install react-focus-lock
-
Import and use in your React component:
import FocusLock from 'react-focus-lock'; function MyComponent() { return ( <FocusLock> <div> <button>Focusable element</button> <input type="text" /> </div> </FocusLock> ); }
-
Customize behavior with props as needed:
<FocusLock returnFocus={true} lockProps={{ onMouseDown: handleMouseDown }}> {/* Your focusable content */} </FocusLock>
Competitor Comparisons
Trap focus within a DOM node.
Pros of focus-trap
- Framework-agnostic, can be used with any JavaScript project
- More flexible and customizable, with a wider range of options
- Larger community and more frequent updates
Cons of focus-trap
- Requires more setup and configuration for React projects
- Less optimized for React-specific use cases
- May have a steeper learning curve for React developers
Code Comparison
react-focus-lock:
import FocusLock from 'react-focus-lock';
<FocusLock>
<div>Focusable content</div>
</FocusLock>
focus-trap:
import createFocusTrap from 'focus-trap';
const trap = createFocusTrap('#modal', {
onActivate: () => modal.classList.add('is-active'),
onDeactivate: () => modal.classList.remove('is-active'),
});
trap.activate();
Summary
focus-trap is a more versatile solution for managing focus in web applications, offering greater flexibility and customization options. However, for React-specific projects, react-focus-lock provides a more streamlined and optimized approach with easier integration. The choice between the two depends on the specific project requirements, framework preferences, and the level of customization needed.
🍿⚛Official React library to use Popper, the positioning library
Pros of react-popper
- More comprehensive positioning solution, offering advanced features like flipping and shifting
- Actively maintained with regular updates and improvements
- Larger community and ecosystem, providing better support and resources
Cons of react-popper
- Larger bundle size due to more features, which may impact performance
- Steeper learning curve for developers new to complex positioning scenarios
- May be overkill for simpler focus management tasks
Code Comparison
react-popper:
import { usePopper } from 'react-popper';
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
const { styles, attributes } = usePopper(referenceElement, popperElement);
react-focus-lock:
import FocusLock from 'react-focus-lock';
<FocusLock>
<div tabIndex="-1">
{/* Focusable content */}
</div>
</FocusLock>
Summary
react-popper is a more feature-rich solution for positioning elements, offering advanced capabilities and broader community support. However, it comes with increased complexity and potential performance overhead. react-focus-lock, on the other hand, is a simpler and more focused library specifically designed for managing focus within components. The choice between the two depends on the specific requirements of your project, with react-popper being better suited for complex positioning needs and react-focus-lock excelling in straightforward focus management scenarios.
Accessible modal dialog component for React
Pros of react-modal
- Provides a complete modal solution with built-in accessibility features
- Offers more customization options for modal appearance and behavior
- Has a larger community and more extensive documentation
Cons of react-modal
- Larger bundle size due to additional features
- May require more setup and configuration for basic use cases
- Less focused on specific accessibility concerns like focus management
Code Comparison
react-modal:
import Modal from 'react-modal';
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="Example Modal"
>
<h2>Modal Content</h2>
<button onClick={closeModal}>Close</button>
</Modal>
react-focus-lock:
import FocusLock from 'react-focus-lock';
<FocusLock>
<div className="modal">
<h2>Modal Content</h2>
<button onClick={closeModal}>Close</button>
</div>
</FocusLock>
Summary
react-modal is a comprehensive solution for creating modals in React applications, offering more features and customization options. It's ideal for projects requiring complex modal functionality.
react-focus-lock, on the other hand, is a lightweight and specialized tool focused on managing focus within components. It's better suited for projects that need precise control over focus behavior or want to implement custom modal-like components.
The choice between the two depends on the specific requirements of your project, with react-modal being more suitable for full-featured modals and react-focus-lock for targeted focus management.
Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
Pros of Material-UI
- Comprehensive UI component library with a wide range of pre-built components
- Follows Material Design principles, providing a consistent and modern look
- Extensive documentation and community support
Cons of Material-UI
- Larger bundle size due to its comprehensive nature
- Steeper learning curve for customization and theming
- May require more setup and configuration for specific use cases
Code Comparison
Material-UI:
import { Button, TextField } from '@mui/material';
<Button variant="contained" color="primary">
Click me
</Button>
<TextField label="Enter text" variant="outlined" />
react-focus-lock:
import FocusLock from 'react-focus-lock';
<FocusLock>
<div tabIndex="-1">
<button>Focused</button>
<button>Button</button>
</div>
</FocusLock>
Key Differences
- Material-UI is a full-fledged UI component library, while react-focus-lock focuses specifically on managing focus within components
- react-focus-lock is more lightweight and easier to integrate for specific focus management needs
- Material-UI provides a complete design system, whereas react-focus-lock is a utility for improving accessibility
Use Cases
- Choose Material-UI for building complete user interfaces with consistent design
- Opt for react-focus-lock when you need to enhance accessibility by managing focus in modal dialogs or other interactive components
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
REACT FOCUS LOCK
- browser friendly focus lock
- matching all your use cases
- trusted by best UI frameworks
- the thing Admiral Ackbar was talking about
It is a trap! We got your focus and will not let him out!
- Modal dialogs. You can not leave it with "Tab", ie do a "tab-out".
- Focused tasks. It will aways brings you back, as you can "lock" user inside a component.
- Any any other case, when you have to lock user intention and focus, if that's what
a11y
is asking for.- Including programatic focus management and smart return focus
Trusted
Trusted by Atlassian AtlasKit, ReachUI, SmoothUI, Storybook and we will do our best to earn your trust too!
Features
- no keyboard control, everything is done watching a focus behavior, not emulating it. Focus-Locks works for all cases including positive tab indexes.
- React Portals support. Even if some data is in outer space - it is still in lock.
- Scattered locks, or focus lock groups - you can setup different isolated locks, and tab from one to another.
- Controllable isolation level.
- variable size bundle. Uses sidecar to trim UI part down to 1.5kb.
ð¡ focus locks is part of a bigger whole, consider scroll lock and text-to-speech lock you have to use to really "lock" the user. Try react-focus-on to archive everything above, assembled in the right order.
How to use
Just wrap something with focus lock, and focus will be moved inside
on mount.
import FocusLock from 'react-focus-lock';
const JailForAFocus = ({onClose}) => (
<FocusLock>
You can not leave this form
<button onClick={onClose} />
</FocusLock>
);
Demo - https://codesandbox.io/s/5wmrwlvxv4.
API
FocusLock would work perfectly even with no props set.
FocusLock has few props to tune behavior, all props are optional:
disabled
, to disable(enable) behavior without altering the tree.className
, to set theclassName
of the internal wrapper.returnFocus
, to return focus into initial position on unmount
By default
returnFocus
is disabled, so FocusLock will not restore original focus on deactivation. This was done mostly to avoid breaking changes. We strong recommend enabling it, to provide a better user experience.
This is expected behavior for Modals, but it is better to implement it by your self. See unmounting and focus management for details
persistentFocus=false
, requires any element to be focused. This also disables text selections inside, and outside focus lock.autoFocus=true
, enables or disables focusing into on Lock activation. If disabled Lock will blur an active focus.noFocusGuards=false
disabled focus guards - virtual inputs which secure tab index.group='''
named focus group for focus scattering aka combined lock targetsshards=[]
an array ofref
pointing to the nodes, which focus lock should consider and a part of it. This is another way focus scattering.whiteList=fn
you could whitelist locations FocusLock should carry about. Everything outside it will ignore. For example - any modals.as='div'
if you need to change internaldiv
element, to any other. Use ref forwarding to give FocusLock the node to work with.lockProps={}
to pass any extra props (except className) to the internal wrapper.hasPositiveIndices=false
to support a focus lock behavior when any elements tabIndex greater than 0.crossFrame=true
enables aggressive focus capturing within iframes
Programmatic control
Focus lock exposes a few methods to control focus programmatically.
Imperative API
useFocusInside(nodeRef)
- to move focus inside the given nodeuseFocusScope():{autofocus, focusNext, focusPrev}
- provides API to manage focus within the current lockuseFocusState()
- manages focus state of a given nodeuseFocusController(nodeRef)
- low level version ofuseFocusScope
working without FocusLock
Declarative API
<AutoFocusInside/>
- causes autofocus to look inside the component<MoveFocusInside/>
- wrapper arounduseFocusInside
, forcibly moves focus inside on mount<FreeFocusInside/>
- hides internals from FocusLock allowing unmanaged focus
Indirect API
Focus-lock behavior can be controlled via data-attributes. Declarative API above is working by setting them for you. See corresponding section in focus-lock for details
Focusing in OSX (Safari/Firefox) is strange!
By default tabbing
in OSX sees
only controls, but not links or anything else tabbable
. This is system settings, and Safari/Firefox obey.
Press Option+Tab in Safari to loop across all tabbables, or change the Safari settings. There is no way to fix Firefox, unless change system settings (Control+F7). See this issue for more information.
Set up
Requirements
- version 1x is React 15/16 compatible
- version 2+ requires React 16.8+ (hooks)
Import
react-focus-lock
exposed 3 entry points: for the classical usage, and a sidecar one.
Default usage
- 4kb,
import FocusLock from 'react-focus-lock
would give you component you are looking for.
Separated usage
Meanwhile - you dont need any focus related logic until it's needed. Thus - you may defer that logic till Lock activation and move all related code to a sidecar.
- UI, 1.5kb,
import FocusLockUI from 'react-focus-lock/UI
- a DOM part of a lock. - Sidecar, 3.5kb,
import Sidecar from 'react-focus-lock/sidecar
- which is the real focus lock.
import FocusLockUI from "react-focus-lock/UI";
import {sidecar} from "use-sidecar";
// prefetch sidecar. data would be loaded, but js would not be executed
const FocusLockSidecar = sidecar(
() => import(/* webpackPrefetch: true */ "react-focus-lock/sidecar")
);
<FocusLockUI
disabled={this.state.disabled}
sideCar={FocusLockSidecar}
>
{content}
</FocusLockUI>
That would split FocusLock into two pieces, reducing app size and improving the first load. The cost of focus-lock is just 1.5kb!
Saved 3.5kb?! ð¤·ââï¸ 3.5kb here and 3.5kb here, and your 20mb bundle is ready.
Autofocus
Use when you cannot use the native autoFocus
prop - because you only want to autofocus once the Trap has been activated
- prop
data-autofocus
on the element. - prop
data-autofocus-inside
on the element to focus on something inside. AutoFocusInside
component, as named export of this library.
import FocusLock, { AutoFocusInside } from 'react-focus-lock';
<FocusLock>
<button>Click</button>
<AutoFocusInside>
<button>will be focused</button>
</AutoFocusInside>
</FocusLock>
// is the same as
<FocusLock>
<button>Click</button>
<button data-autofocus>will be focused</button>
</FocusLock>
If there is more than one auto-focusable target - the first will be selected. If it is a part of radio group, and rest of radio group element are also autofocusable(just put them into AutoFocusInside) - checked one fill be selected.
AutoFocusInside
will work only on Lock activation, and does nothing, then used outside of the lock.
You can use MoveFocusInside
to move focus inside with or without lock.
import { MoveFocusInside } from 'react-focus-lock';
<MoveFocusInside>
<button>will be focused</button>
</MoveFocusInside>
Portals
Use focus scattering to handle portals
- using
groups
. Just create a few locks (only one could be active) with a same group name
const PortaledElement = () => (
<FocusLock group="group42" disabled={true}>
// "discoverable" portaled content
</FocusLock>
);
<FocusLock group="group42">
// main content
</FocusLock>
- using
shards
. Just pass all the pieces to the "shards" prop.
const PortaledElement = () => (
<div ref={ref}>
// "discoverable" portaled content
</div>
);
<FocusLock shards={[ref]}>
// main content
</FocusLock>
- without anything. FocusLock will not prevent focusing portaled element, but will not include them in to tab order
const PortaledElement = () => (
<div>
// NON-"discoverable" portaled content
</div>
);
<FocusLock shards={[ref]}>
// main content
<PortaledElement />
</FocusLock>
Using your own Components
You may use as
prop to change what Focus-Lock will render around children
.
<FocusLock as="section">
<button>Click</button>
<button data-autofocus>will be focused</button>
</FocusLock>
<FocusLock as={AnotherComponent} lockProps={{anyAnotherComponentProp: 4}}>
<button>Click</button>
<span>Hello there!</span>
</FocusLock>
Programmatic Control
Let's take a look at the Rowing Focus
as an example.
// Will set tabindex to -1 when is not focused
const FocusTrackingButton = ({ children }) => {
const { active, onFocus, ref } = useFocusState();
return (
<button tabIndex={active ? undefined : -1} onFocus={onFocus} ref={ref}>
{children}
</button>
);
};
const RowingFocusInternalTrap = () => {
const { autoFocus, focusNext, focusPrev } = useFocusScope();
// use useFocusController(divRef) if there is no FocusLock around
useEffect(() => {
autoFocus();
}, []);
const onKey = (event) => {
if (event.key === 'ArrowDown') {
focusNext({ onlyTabbable: false });
}
if (event.key === 'ArrowUp') {
focusPrev({ onlyTabbable: false });
}
};
return (
<div
onKeyDown={onKey}
// ref={divRef} for useFocusController
>
<FocusButton>Button1</FocusButton>
<FocusButton>Button2</FocusButton>
<FocusButton>Button3</FocusButton>
<FocusButton>Button4</FocusButton>
</div>
);
};
// FocusLock, even disabled one
const RowingFocusTrap = () => (
<FocusLock disabled>
<RowingFocusInternalTrap />
</FocusLock>
);
Guarding
As you may know - FocusLock is adding Focus Guards
before and after lock to remove some side effects, like page scrolling.
But shards
will not have such guards, and it might be not so cool to use them - for example if no tabbable
would be
defined after shard - you will tab to the browser chrome.
You may wrap shard with InFocusGuard
or just drop InFocusGuard
here and there - that would solve the problem.
import {InFocusGuard} from 'react-focus-lock';
// wrap with
<InFocusGuard>
<button />
</InFocusGuard>
// place before and after
<InFocusGuard />
<button />
<InFocusGuard />
InFocusGuards would be active(tabbable) only when tabble, it protecting, is focused.
Removing Tailing Guard
If only your modal is the last tabble element on the body - you might remove the Tailing Guard, to allow user tab into address bar.
<InFocusGuard/>
<button />
// there is no "tailing" guard :)
Unmounting and focus management
- In case FocusLock has
returnFocus
enabled, and it's going to be unmounted - focus will be returned after zero-timeout. - In case
returnFocus
is set tofalse
, and you are going to control focus change on your own - keep in mind
React will first call Parent.componentWillUnmount, and next Child.componentWillUnmount
This means - Trap will be still active by the time you may want move(return) focus on componentWillUnmount. Please deffer this action with a zero-timeout.
Similarly, if you are using the disabled
prop to control FocusLock, you will need a zero-timeout to correctly restore focus.
<FocusLock
disabled={isFocusLockDisabled}
onDeactivation={() => {
// Without the zero-timeout, focus will likely remain on the button/control
// you used to set isFocusLockDisabled = true
window.setTimeout(() => myRef.current.focus(), 0);
}
>
Return focus to another node
In some cases the original node that was focused before the lock was activated is not the desired node to return focus to. Some times this node might not exists at all.
- first of all, FocusLock need a moment to record this node, please do not hide it onClick, but hide onBlur (Dropdown, looking at you)
- second, you may specify a callback as
returnFocus
, letting you decide where to return focus to.
<FocusLock
returnFocus={(suggestedNode) => {
// somehow activeElement should not be changed
if(document.activeElement.hasAttributes('main-content')) {
// opt out from default behavior
return false;
}
if (someCondition(suggestedNode)) {
// proceed with the suggested node
return true;
}
// handle return focus manually
document.getElementById('the-button').focus();
// opt out from default behavior
return false;
}}
/>
Return focus with no scroll
read more at the issue #83 or mdn article.
To return focus, but without jumpy page scroll returning a focus you might specify a focus option
<FocusLock
returnFocus={{ preventScroll: false }} // working not in all browsers
>
Not supported by Edge and Safari.
Focus fighting
Two different focus-lock-managers or even different version of a single one, being active simultaneously will FIGHT for the focus. This usually totally breaks user experience.
React-Focus-Lock will automatically surrender, letting another library to take the lead.
Resolving focus fighting
You may wrap some render branch with FreeFocusInside
, and react-focus-lock will ignore
any focus inside marked node. So in case focus moves to uncontrolled location focus-lock will not trigger letting another library to act without interference in that another location.
import { FreeFocusInside } from 'react-focus-lock';
<FreeFocusInside>
<div id="portal-for-modals">
in this div i am going to portal my modals, dont fight with them please
</div>
</FreeFocusInside>
Another option for hybrid applications is to whiteList
area where Focus-Lock should act, automatically allowing other managers in other areas.
The code below will scope Focus-Lock on inside the (react)root
element, so anything jQuery can add to the body will be ignored.
<FocusLock whiteList={node => document.getElementById('root').contains(node)}>
...
</FocusLock>
Two Focus-Locks
React-Focus-Lock is expected to be a singlentone. __Use webpack or yarn resolution for force only one version of react-focus-lock used.
webpack.conf
resolve: {
alias: {
'react-focus-lock': path.resolve(path.join(__dirname, './node_modules/react-focus-lock'))
...
WHY?
From MDN Article about accessible dialogs:
- The dialog must be properly labeled
- Keyboard focus must be managed correctly
This one is about managing the focus.
I've got a good article about focus management, dialogs and WAI-ARIA.
Not only for React
Uses focus-lock under the hood. It does also provide support for Vue.js and Vanilla DOM solutions
More
To create a "right" modal dialog you have to:
- manage a focus. Use this library
- block document scroll. Use react-remove-scroll.
- hide everything else from screen readers. Use aria-hidden
You may use react-focus-on to achieve everything above, assembled in the right order.
Licence
MIT
Top Related Projects
Trap focus within a DOM node.
🍿⚛Official React library to use Popper, the positioning library
Accessible modal dialog component for React
Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot