Modals you can await.

A headless, zero-dependency modal stack for React. Open a modal like you call a function, and get its answer back.

npm install react-easy-modals
project-settings.tsx
const modals = useModals();​async function deleteProject() {  const answer = await modals.open(ConfirmDelete, {    project: "acme-web",  });  if (answer === "delete") removeProject();}

Run it: the modal opens, and the promise waits for its answer.

Three steps to your first modal

No open state, no trigger component, no callback props.

  1. 1

    Install

    One package, no dependencies besides React.

    npm install react-easy-modals
  2. 2

    Add the provider

    It renders the stack at the root of your app.

    <ModalProvider>
      <App />
    </ModalProvider>
  3. 3

    Open a modal

    Any component works. Its close value is the result.

    const ok = await modals.open(Confirm);

The rest of the stack, when you need it

Every feature below is running live. Open the demos and try them.

A new modal hides the one below it until it closes.

open modals: 0
modals.open(Step1);
modals.open(Step2); // Step1 waits underneath
modals.close();     // back to Step1
How the stack works

Already on shadcn/ui?

Add a Modal built on your Dialog. The registry serves the Radix or Base UI version to match the style in your components.json.

npx shadcn@latest add @react-easy-modals/modal
Read the registry guide
settings-page.tsx
Removed: <Dialog open={open} onOpenChange={setOpen}>Removed:   <DialogContent>…</DialogContent>Removed: </Dialog>Added: const result = await modals.open(MyModal);