react-aria vs react-focus-lock vs react-modal vs react-portal
Building Accessible Overlays and Portals in React
react-ariareact-focus-lockreact-modalreact-portalSimilar Packages:

Building Accessible Overlays and Portals in React

react-aria, react-focus-lock, react-modal, and react-portal are tools for managing overlays, modals, and DOM hierarchy in React applications. react-aria provides a comprehensive set of accessibility hooks and primitives for building custom components. react-focus-lock specializes in trapping focus within a specific container. react-modal offers a ready-made accessible modal component. react-portal allows rendering components outside the main DOM tree, though this is now a built-in React feature.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-aria2,535,63614,99714.4 MB585a day agoApache-2.0
react-focus-lock01,387111 kB115 months agoMIT
react-modal07,413188 kB211a year agoMIT
react-portal02,15039.2 kB16a year agoMIT

Building Accessible Overlays and Portals in React

When building complex interfaces like modals, drawers, or dropdowns, you often need to render content outside the normal DOM hierarchy and manage keyboard focus carefully. The packages react-aria, react-focus-lock, react-modal, and react-portal all address parts of this challenge, but they serve different roles in a modern architecture. Let's compare how they handle rendering, focus, and accessibility.

πŸšͺ Rendering Outside the Hierarchy: Portals

Portals allow you to render a component in a different part of the DOM, which is critical for modals to avoid CSS clipping issues.

react-portal provides a dedicated <Portal> component to handle this.

  • You wrap content in the component, and it moves to the target node.
  • This was essential before React 16, but now duplicates built-in features.
// react-portal: Dedicated component
import Portal from 'react-portal';

function MyWidget() {
  return (
    <Portal>
      <div className="overlay">Content outside root</div>
    </Portal>
  );
}

react-modal handles portals internally within the <Modal> component.

  • You do not manage the portal node manually.
  • The library appends the modal to document.body by default.
// react-modal: Internal portal handling
import Modal from 'react-modal';

function MyModal() {
  return (
    <Modal isOpen={true}>
      <div>Content appended to body</div>
    </Modal>
  );
}

react-aria does not provide a Portal component.

  • It expects you to use React's built-in ReactDOM.createPortal.
  • This reduces bundle size and relies on stable React APIs.
// react-aria: Native React Portal
import { createPortal } from 'react-dom';

function MyOverlay({ container }) {
  return createPortal(
    <div>Content managed by aria hooks</div>,
    container
  );
}

react-focus-lock does not handle portals itself.

  • It focuses on focus management within whatever container you provide.
  • You typically nest it inside a portal created by another method.
// react-focus-lock: No portal logic
import FocusLock from 'react-focus-lock';

function MyDrawer() {
  return (
    <FocusLock>
      <div>Focus trapped here</div>
    </FocusLock>
  );
}

πŸ”’ Focus Management: Trapping and Restoration

When a modal opens, focus should move inside it. When it closes, focus should return to the trigger button. This is critical for keyboard users.

react-focus-lock specializes in this behavior.

  • It automatically traps focus inside the locked component.
  • It restores focus to the previous element when unmounted.
// react-focus-lock: Automatic trap
import FocusLock from 'react-focus-lock';

function Drawer() {
  return (
    <FocusLock>
      <button>First</button>
      <button>Last</button>
    </FocusLock>
  );
}

react-aria provides hooks for manual control.

  • useFocusManager and useModal help coordinate focus behavior.
  • Gives you more control but requires more setup code.
// react-aria: Hook-based management
import { useModal } from '@react-aria/overlays';

function ModalContent() {
  const { modalProps } = useModal();
  return (
    <div {...modalProps}>
      <button>Focus managed by hook</button>
    </div>
  );
}

react-modal handles focus automatically.

  • It traps focus when open and restores it when closed.
  • Configurable via props like appElement.
// react-modal: Built-in focus handling
import Modal from 'react-modal';

function App() {
  Modal.setAppElement('#root');
  return <Modal isOpen={true}>Content with trapped focus</Modal>;
}

react-portal has no focus management features.

  • It only moves DOM nodes.
  • You must implement focus trapping separately (e.g., with react-focus-lock).
// react-portal: No focus features
import Portal from 'react-portal';

function Overlay() {
  return <Portal><div>No focus logic included</div></Portal>;
}

β™Ώ Accessibility Standards: ARIA Attributes

Screen readers need to know when a modal is open and what content is relevant.

react-aria is the most robust for accessibility.

  • Hooks automatically apply correct ARIA attributes (roles, states).
  • Ensures compliance with modern WCAG standards.
// react-aria: Auto ARIA attributes
import { useOverlay } from '@react-aria/overlays';

function Overlay(props) {
  const { overlayProps } = useOverlay(props);
  return <div {...overlayProps} role="dialog">Accessible Dialog</div>;
}

react-modal includes basic accessibility support.

  • Sets role="dialog" and handles aria-modal.
  • Good for simple use cases but less flexible than hooks.
// react-modal: Basic ARIA support
import Modal from 'react-modal';

function Dialog() {
  return <Modal role="dialog" aria-label="Settings">Content</Modal>;
}

react-focus-lock supports accessibility via focus.

  • Ensures keyboard users don't get stuck behind the overlay.
  • Does not manage ARIA roles directly.
// react-focus-lock: Focus accessibility
import FocusLock from 'react-focus-lock';

function Trap() {
  return <FocusLock><div role="region">Focus Region</div></FocusLock>;
}

react-portal has no accessibility features.

  • It is purely a DOM rendering tool.
  • You must manage all ARIA attributes manually.
// react-portal: Manual ARIA
import Portal from 'react-portal';

function A11yOverlay() {
  return <Portal><div role="alert">Manual ARIA needed</div></Portal>;
}

πŸ› οΈ Maintenance and Future Proofing

Choosing a library also means choosing its long-term viability.

react-aria is actively maintained by Adobe.

  • Regularly updated to match latest React features.
  • The recommended choice for new, accessible component systems.

react-focus-lock is actively maintained.

  • Still the go-to for specific focus trapping needs.
  • Works well in conjunction with react-aria.

react-modal is in maintenance mode.

  • Receives security fixes but few new features.
  • Risk of technical debt in long-term projects.

react-portal is functionally obsolete.

  • React 16+ includes createPortal in the core API.
  • Adding this dependency is unnecessary for new projects.

πŸ“Š Summary Table

Featurereact-ariareact-focus-lockreact-modalreact-portal
Portal RenderingNative (createPortal)NoneBuilt-inComponent (<Portal>)
Focus TrappingHooks (useFocusManager)Component (<FocusLock>)Built-inNone
ARIA AttributesAutomatic (Hooks)ManualAutomaticManual
Maintenanceβœ… Activeβœ… Active⚠️ Maintenance❌ Obsolete
Best ForCustom Design SystemsFocus TrappingQuick ModalsLegacy React (<16)

πŸ’‘ The Big Picture

react-aria is the modern standard for building accessible components from scratch. It gives you the logic without forcing a specific UI, making it perfect for custom design systems.

react-focus-lock remains a valuable utility for specific focus trapping scenarios, often used alongside react-aria or other headless libraries.

react-modal is a legacy solution. While it works, it lacks the flexibility of modern hook-based approaches and is no longer the primary recommendation for new architecture.

react-portal should be avoided. The functionality it provided is now a core feature of React itself. Using the native ReactDOM.createPortal is safer and removes an unnecessary dependency.

Final Thought: For new projects, combine react-aria for logic and accessibility with React's native portals. Use react-focus-lock only if you need additional focus control that the aria hooks do not cover. Avoid react-modal and react-portal to keep your stack modern and lean.

How to Choose: react-aria vs react-focus-lock vs react-modal vs react-portal

  • react-aria:

    Choose react-aria if you are building a custom design system and need robust, accessible primitives. It offers the most modern approach to accessibility (a11y) with hooks for modals, focus management, and keyboard interactions. Ideal for teams that want full control over markup while ensuring WCAG compliance.

  • react-focus-lock:

    Choose react-focus-lock if you need a dedicated solution for trapping focus within a specific area, such as a drawer or custom modal. It is lightweight and works well alongside other libraries that handle the actual rendering and overlay behavior.

  • react-modal:

    Choose react-modal if you need a quick, drop-in modal solution for a legacy project or a simple application. However, note that it is in maintenance mode and may not support the latest React patterns as well as newer libraries.

  • react-portal:

    Avoid react-portal for new projects. Since React 16, portal functionality is built directly into React via ReactDOM.createPortal. Use the native API instead to reduce dependencies and improve long-term maintainability.

README for react-aria

React Aria

A library of React Hooks that provides accessible UI primitives for your design system.

Features

  • ♿️ Accessible – React Aria provides accessibility and behavior according to WAI-ARIA Authoring Practices, including full screen reader and keyboard navigation support. All components have been tested across a wide variety of screen readers and devices to ensure the best experience possible for all users.
  • πŸ“± Adaptive – React Aria ensures consistent behavior, no matter the UI. It supports mouse, touch, keyboard, and screen reader interactions that have been tested across a wide variety of browsers, devices, and platforms.
  • 🌍 International – React Aria supports over 30 languages, including right-to-left-specific behavior, internationalized date and number formatting, and more.
  • 🎨 Fully customizable – React Aria doesn’t implement any rendering or impose a DOM structure, styling methodology, or design-specific details. It provides behavior, accessibility, and interactions and lets you focus on your design.

Getting started

The easiest way to start building a component library with React Aria is by following our getting started guide. It walks through all of the steps needed to install the hooks from npm, and create your first component.

Example

Here is a very basic example of using React Aria.

import {useButton} from '@react-aria/button';

function Button(props) {
  let ref = React.useRef();
  let {buttonProps} = useButton(props, ref);

  return (
    <button {...buttonProps} ref={ref}>
      {props.children}
    </button>
  );
}

<Button onPress={() => alert('Button pressed!')}>Press me</Button>

Learn more

React Aria is part of a family of libraries that help you build adaptive, accessible, and robust user experiences. Learn more about React Spectrum and React Stately on our website.