react-modal vs react-modal-hook vs react-modal-promise
Managing Modal Dialogs in React Applications
react-modalreact-modal-hookreact-modal-promise
Managing Modal Dialogs in React Applications

react-modal, react-modal-hook, and react-modal-promise are all npm packages designed to simplify the implementation of modal dialogs in React applications, but they differ significantly in architecture, usage patterns, and integration style. react-modal is a general-purpose, render-prop-based modal component with strong accessibility support. react-modal-hook leverages React hooks to manage modal state and rendering declaratively within component trees. react-modal-promise enables modals to return values via JavaScript Promises, making them ideal for confirmation dialogs or form inputs that need to resolve asynchronously.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
react-modal2,320,6997,406188 kB204a year agoMIT
react-modal-hook39,75225445.6 kB25-MIT
react-modal-promise33,2156858 kB17-MIT

Managing Modal Dialogs in React: react-modal vs react-modal-hook vs react-modal-promise

Modals are a common UI pattern, but implementing them correctly in React—especially with proper focus management, accessibility, and state isolation—can be tricky. The three packages under review each solve this problem differently: one as a traditional component (react-modal), one using hooks (react-modal-hook), and one treating modals as async functions (react-modal-promise). Let’s compare how they work in practice.

🧱 Core Architecture: Component vs Hook vs Promise

react-modal is a standalone React component that renders a modal dialog when its isOpen prop is true. You control visibility via local or global state, and it handles ARIA attributes, focus trapping, and overlay clicks out of the box.

// react-modal: Controlled component
import React, { useState } from 'react';
import Modal from 'react-modal';

function App() {
  const [modalIsOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setIsOpen(true)}>Open Modal</button>
      <Modal isOpen={modalIsOpen} onRequestClose={() => setIsOpen(false)}>
        <h2>Hello</h2>
        <button onClick={() => setIsOpen(false)}>Close</button>
      </Modal>
    </div>
  );
}

react-modal-hook uses custom hooks to separate modal logic from rendering. You define a modal once and trigger it anywhere using a hook-generated function.

// react-modal-hook: Hook-based modal
import { useModal } from 'react-modal-hook';

function ConfirmModal({ onConfirm, onCancel }) {
  return (
    <div className="modal">
      <p>Are you sure?</p>
      <button onClick={onConfirm}>Yes</button>
      <button onClick={onCancel}>No</button>
    </div>
  );
}

function App() {
  const [showConfirm, hideConfirm] = useModal(() => (
    <ConfirmModal
      onConfirm={() => { console.log('Confirmed'); hideConfirm(); }}
      onCancel={hideConfirm}
    />
  ));

  return <button onClick={showConfirm}>Delete Item</button>;
}

react-modal-promise wraps modals in a Promise interface. Calling a modal function returns a Promise that resolves when the user takes action.

// react-modal-promise: Async modal
import { useModalPromise } from 'react-modal-promise';

function ConfirmDialog({ isOpen, onResolve }) {
  if (!isOpen) return null;
  return (
    <div className="modal">
      <p>Delete this item?</p>
      <button onClick={() => onResolve(true)}>Yes</button>
      <button onClick={() => onResolve(false)}>No</button>
    </div>
  );
}

function App() {
  const confirm = useModalPromise(ConfirmDialog);

  const handleDelete = async () => {
    const result = await confirm();
    if (result) {
      // Proceed with deletion
    }
  };

  return <button onClick={handleDelete}>Delete</button>;
}

⚠️ Important: As of 2024, react-modal-hook appears to be deprecated. Its GitHub repository is archived, and there have been no npm releases since 2020. New projects should avoid it unless maintaining legacy code.

🔌 Integration Requirements

Each package imposes different structural requirements on your app.

  • react-modal requires you to call Modal.setAppElement() once at startup to ensure screen readers ignore background content. This is a one-time setup but easy to forget.
// Required for accessibility
import Modal from 'react-modal';
Modal.setAppElement('#root');
  • react-modal-hook needs no global setup, but modals are rendered outside the triggering component’s subtree (typically appended to document.body), which can complicate styling or context access.

  • react-modal-promise requires a <ModalProvider> at the root of your app to manage the modal queue:

// react-modal-promise: Provider required
import { ModalProvider } from 'react-modal-promise';

function Root() {
  return (
    <ModalProvider>
      <App />
    </ModalProvider>
  );
}

♿ Accessibility and Focus Management

react-modal has the most robust accessibility support. It automatically:

  • Traps focus inside the modal
  • Restores focus to the triggering element on close
  • Sets aria-hidden="true" on background content
  • Supports role="dialog" and aria-modal="true"

react-modal-hook does not include built-in accessibility features. You must manually implement focus trapping and ARIA attributes.

react-modal-promise also lacks native accessibility utilities. Since it’s a thin wrapper around your own modal component, accessibility depends entirely on your implementation.

🔄 State and Data Flow

  • With react-modal, modal state lives in your component (or global store). Data flows in via props; actions flow out via callbacks.

  • react-modal-hook encapsulates state inside the hook. You pass callbacks to the modal component to communicate back.

  • react-modal-promise inverts control: instead of passing callbacks, you resolve(value) from inside the modal, and the calling code receives the value via await.

This makes react-modal-promise especially clean for simple yes/no or input dialogs:

// Example: Login modal that returns credentials
const login = useModalPromise(LoginModal);

const creds = await login();
if (creds) {
  authenticate(creds);
}

Whereas with react-modal, you’d need to lift state or use refs:

// More verbose with react-modal
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = () => {
  authenticate({ email, password });
  setIsOpen(false);
};

🛠️ When to Use Which

ScenarioBest Choice
Enterprise app requiring WCAG compliancereact-modal
Simple confirmation that resolves to true/falsereact-modal-promise
Modals deeply integrated into component statereact-modal
Legacy project already using react-modal-hookreact-modal-hook (but migrate if possible)
Async-heavy workflow (e.g., wizards, chained prompts)react-modal-promise

💡 Final Recommendation

For new projects, your safest bet is react-modal—it’s actively maintained, battle-tested, and handles accessibility correctly by default. If your use case involves frequent modal interactions that return values (like confirmations or small forms), layer react-modal-promise on top of your own accessible modal component—but don’t rely on it for accessibility.

Avoid react-modal-hook in new code due to its unmaintained status. While its hook-based API was innovative in 2019, modern alternatives like @radix-ui/react-dialog or custom hook wrappers around react-modal offer better long-term viability.

Remember: no modal library replaces thoughtful UX. Always test with keyboard navigation and screen readers, regardless of which package you choose.

How to Choose: react-modal vs react-modal-hook vs react-modal-promise
  • react-modal:

    Choose react-modal if you need a mature, accessible, and highly customizable modal component that integrates cleanly into your JSX without requiring architectural changes. It’s well-suited for applications where modals are rendered as part of the component tree and controlled via standard React state. Avoid it if you prefer hook-based abstractions or need modals to resolve like async functions.

  • react-modal-hook:

    Choose react-modal-hook if you want to manage modals using React hooks and avoid lifting modal state to parent components. It’s ideal for teams already using a hooks-first approach and who value clean separation between modal logic and presentation. However, note that this package appears unmaintained — its GitHub repository has been archived and the npm page shows no recent updates — so use caution in new projects.

  • react-modal-promise:

    Choose react-modal-promise when you need modals to behave like asynchronous functions that resolve with user input (e.g., confirmations, login prompts, or data entry). This pattern simplifies control flow by turning UI interactions into Promise-based workflows. It’s particularly useful in utility-driven code or when integrating modals into async-heavy logic, but requires wrapping modal content in a special provider.

README for react-modal

react-modal

Accessible modal dialog component for React.JS

Build Status Coverage Status gzip size Join the chat at https://gitter.im/react-modal/Lobby

Table of Contents

Installation

To install, you can use npm or yarn:

$ npm install --save react-modal
$ yarn add react-modal

To install react-modal in React CDN app:

  • Add this CDN script tag after React CDN scripts and before your JS files (for example from cdnjs):

       <script src="https://cdnjs.cloudflare.com/ajax/libs/react-modal/3.14.3/react-modal.min.js"
       integrity="sha512-MY2jfK3DBnVzdS2V8MXo5lRtr0mNRroUI9hoLVv2/yL3vrJTam3VzASuKQ96fLEpyYIT4a8o7YgtUs5lPjiLVQ=="
       crossorigin="anonymous"
       referrerpolicy="no-referrer"></script>
    
  • Use <ReactModal> tag inside your React CDN app.

API documentation

The primary documentation for react-modal is the reference book, which describes the API and gives examples of its usage.

Examples

Here is a simple example of react-modal being used in an app with some custom styles and focusable input elements within the modal content:

import React from 'react';
import ReactDOM from 'react-dom';
import Modal from 'react-modal';

const customStyles = {
  content: {
    top: '50%',
    left: '50%',
    right: 'auto',
    bottom: 'auto',
    marginRight: '-50%',
    transform: 'translate(-50%, -50%)',
  },
};

// Make sure to bind modal to your appElement (https://reactcommunity.org/react-modal/accessibility/)
Modal.setAppElement('#yourAppElement');

function App() {
  let subtitle;
  const [modalIsOpen, setIsOpen] = React.useState(false);

  function openModal() {
    setIsOpen(true);
  }

  function afterOpenModal() {
    // references are now sync'd and can be accessed.
    subtitle.style.color = '#f00';
  }

  function closeModal() {
    setIsOpen(false);
  }

  return (
    <div>
      <button onClick={openModal}>Open Modal</button>
      <Modal
        isOpen={modalIsOpen}
        onAfterOpen={afterOpenModal}
        onRequestClose={closeModal}
        style={customStyles}
        contentLabel="Example Modal"
      >
        <h2 ref={(_subtitle) => (subtitle = _subtitle)}>Hello</h2>
        <button onClick={closeModal}>close</button>
        <div>I am a modal</div>
        <form>
          <input />
          <button>tab navigation</button>
          <button>stays</button>
          <button>inside</button>
          <button>the modal</button>
        </form>
      </Modal>
    </div>
  );
}

ReactDOM.render(<App />, appElement);

You can find more examples in the examples directory, which you can run in a local development server using npm start or yarn run start.

Demos

There are several demos hosted on CodePen which demonstrate various features of react-modal: