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.
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.
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.
// 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.
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.
ReactDOM.createPortal.// 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.
// react-focus-lock: No portal logic
import FocusLock from 'react-focus-lock';
function MyDrawer() {
return (
<FocusLock>
<div>Focus trapped here</div>
</FocusLock>
);
}
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.
// 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.// 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.
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.
react-focus-lock).// react-portal: No focus features
import Portal from 'react-portal';
function Overlay() {
return <Portal><div>No focus logic included</div></Portal>;
}
Screen readers need to know when a modal is open and what content is relevant.
react-aria is the most robust for accessibility.
// 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.
role="dialog" and handles aria-modal.// 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.
// 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.
// react-portal: Manual ARIA
import Portal from 'react-portal';
function A11yOverlay() {
return <Portal><div role="alert">Manual ARIA needed</div></Portal>;
}
Choosing a library also means choosing its long-term viability.
react-aria is actively maintained by Adobe.
react-focus-lock is actively maintained.
react-aria.react-modal is in maintenance mode.
react-portal is functionally obsolete.
createPortal in the core API.| Feature | react-aria | react-focus-lock | react-modal | react-portal |
|---|---|---|---|---|
| Portal Rendering | Native (createPortal) | None | Built-in | Component (<Portal>) |
| Focus Trapping | Hooks (useFocusManager) | Component (<FocusLock>) | Built-in | None |
| ARIA Attributes | Automatic (Hooks) | Manual | Automatic | Manual |
| Maintenance | β Active | β Active | β οΈ Maintenance | β Obsolete |
| Best For | Custom Design Systems | Focus Trapping | Quick Modals | Legacy React (<16) |
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.
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.
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.
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.
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.
A library of React Hooks that provides accessible UI primitives for your design system.
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.
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>
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.