react-focus-lock vs react-modal vs react-scrolllock vs react-aria-modal
React Modal Management Libraries Comparison
1 Year
react-focus-lockreact-modalreact-scrolllockreact-aria-modalSimilar Packages:
What's React Modal Management Libraries?

These libraries provide various functionalities for managing modal dialogs in React applications. They focus on accessibility, focus management, and user experience, ensuring that modals behave correctly and are usable across different devices and screen readers. Each library offers unique features tailored to different needs, such as accessibility compliance, focus trapping, and scroll management, making it easier for developers to implement modals that enhance user interaction without compromising accessibility standards.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
react-focus-lock2,143,6231,347111 kB53 months agoMIT
react-modal1,750,4487,407188 kB1995 months agoMIT
react-scrolllock94,972462-375 years agoMIT
react-aria-modal28,4231,04147.8 kB322 years agoMIT
Feature Comparison: react-focus-lock vs react-modal vs react-scrolllock vs react-aria-modal

Accessibility

  • react-focus-lock:

    react-focus-lock focuses on keeping keyboard users within the modal. It traps focus inside the modal, preventing users from tabbing to elements outside of it, which is essential for accessibility and usability in keyboard navigation scenarios.

  • react-modal:

    react-modal includes basic ARIA attributes and allows for keyboard navigation, but it may require additional configuration to meet all accessibility standards. It is a good starting point for developers looking to implement modals without extensive accessibility features.

  • react-scrolllock:

    react-scrolllock does not directly address accessibility but works in conjunction with other libraries to prevent background scrolling, which can enhance the user experience when a modal is open.

  • react-aria-modal:

    react-aria-modal is built with accessibility in mind, providing ARIA roles and properties to ensure that screen readers can interpret the modal correctly. It includes features like focus management and keyboard navigation, making it compliant with WCAG standards.

Focus Management

  • react-focus-lock:

    react-focus-lock provides robust focus management by locking focus within the modal. It allows for smooth navigation through modal elements while preventing focus from escaping, which is particularly beneficial for accessibility.

  • react-modal:

    react-modal requires manual management of focus, which can be a drawback for developers. While it does provide some focus management capabilities, it may not be as comprehensive as other options.

  • react-scrolllock:

    react-scrolllock does not manage focus but complements other libraries by ensuring that background content does not scroll, allowing developers to focus on implementing modals without worrying about scroll behavior.

  • react-aria-modal:

    react-aria-modal automatically manages focus when the modal opens and closes, ensuring that focus is set to the first focusable element within the modal. This behavior is crucial for users relying on keyboard navigation.

Ease of Use

  • react-focus-lock:

    react-focus-lock is straightforward to implement and can be easily integrated with existing modals. It requires minimal configuration, making it user-friendly for developers.

  • react-modal:

    react-modal is known for its simplicity and ease of use. It provides a quick way to implement modals with basic functionality, making it suitable for projects that require minimal setup.

  • react-scrolllock:

    react-scrolllock is extremely easy to use, requiring only a few lines of code to implement. It is lightweight and does not add complexity to modal management.

  • react-aria-modal:

    react-aria-modal has a steeper learning curve due to its focus on accessibility features. Developers may need to familiarize themselves with ARIA practices to use it effectively, but the benefits for accessibility are significant.

Customization

  • react-focus-lock:

    react-focus-lock offers limited customization as it primarily focuses on managing focus. However, it can be combined with other libraries to create a fully customized modal experience.

  • react-modal:

    react-modal provides a good level of customization, allowing developers to style the modal and control its behavior through props. It is flexible enough for most use cases without being overly complex.

  • react-scrolllock:

    react-scrolllock is not customizable as it focuses solely on scroll management. Its simplicity is its strength, but it does not provide additional features for modal customization.

  • react-aria-modal:

    react-aria-modal allows for extensive customization options, enabling developers to tailor the modal's appearance and behavior to fit their application's needs while ensuring accessibility is maintained.

Performance

  • react-focus-lock:

    react-focus-lock is lightweight and efficient, ensuring that it does not add significant overhead to modal implementations. It performs well even in complex applications with multiple modals.

  • react-modal:

    react-modal is performant for basic use cases, but developers should be cautious of rendering issues if not managed properly, especially in larger applications with many modals.

  • react-scrolllock:

    react-scrolllock is very lightweight and does not impact performance significantly. It is designed to be a minimal solution for scroll management, making it an excellent choice for performance-sensitive applications.

  • react-aria-modal:

    react-aria-modal is optimized for performance, but its focus on accessibility may introduce some overhead. However, the trade-off is often worth it for applications that prioritize usability for all users.

How to Choose: react-focus-lock vs react-modal vs react-scrolllock vs react-aria-modal
  • react-focus-lock:

    Select react-focus-lock when you need to manage focus within a modal effectively. This package ensures that keyboard navigation is confined to the modal while it is open, preventing focus from escaping, which is crucial for accessibility and user experience.

  • react-modal:

    Opt for react-modal if you are looking for a straightforward and widely-used solution. It is simple to implement and offers basic modal functionality with some accessibility features, making it a good choice for general use cases without complex requirements.

  • react-scrolllock:

    Use react-scrolllock if your primary concern is preventing background scrolling when a modal is open. This package is lightweight and focuses solely on managing scroll behavior, making it ideal for applications that need to maintain a clean user experience without additional modal features.

  • react-aria-modal:

    Choose react-aria-modal if accessibility is your top priority. It is designed to provide a fully accessible modal experience, adhering to ARIA standards, making it suitable for applications that must comply with accessibility guidelines.

README for react-focus-lock

REACT FOCUS LOCK

it-is-a-trap
  • browser friendly focus lock
  • matching all your use cases
  • trusted by best UI frameworks
  • the thing Admiral Ackbar was talking about

CircleCI status npm bundle size downloads


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 the className 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 targets
  • shards=[] an array of ref 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 internal div 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 node
  • useFocusScope():{autofocus, focusNext, focusPrev} - provides API to manage focus within the current lock
  • useFocusState() - manages focus state of a given node
  • useFocusController(nodeRef) - low level version of useFocusScope working without FocusLock

Declarative API

  • <AutoFocusInside/> - causes autofocus to look inside the component
  • <MoveFocusInside/> - wrapper around useFocusInside, 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 to false, 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:

You may use react-focus-on to achieve everything above, assembled in the right order.

Licence

MIT