Ariakit
/

Nested Dialog

Rendering a modal Dialog to confirm an action inside another modal dialog using React.

Open preview in a new tab
Edit withViteNext.js
import "./style.css";
import { useState } from "react";
import {
} from "@ariakit/react";
export default function Example() {
const [cartOpen, setCartOpen] = useState(false);
const [removing, setRemoving] = useState("");
const [products, setProducts] = useState([
"Summer T-Shirt",
"Warm Jacket",
"Slim Shorts",
]);
const confirmRemove = () => {
setProducts((products) => {
return products.filter((product) => product !== removing);
});
};
return (
<>
<Button onClick={() => setCartOpen(true)} className="button">
View Cart
</Button>
open={cartOpen}
onClose={() => setCartOpen(false)}
backdrop={<div className="backdrop" />}
className="dialog"
>
<div className="header">
<DialogHeading className="heading">Your Shopping Cart</DialogHeading>
<DialogDismiss className="button secondary dismiss" />
</div>
<table className="table">
<tbody className="table-body">
{products.map((product) => (
<tr key={product}>
<td className="table-cell">{product}</td>
<td className="table-cell">
onClick={() => setRemoving(product)}
className="button danger"
>
Remove
</Button>
</td>
</tr>
))}
</tbody>
</table>
<DialogDismiss className="button primary">Checkout</DialogDismiss>
</Dialog>
open={!!removing}
onClose={() => setRemoving("")}
backdrop={<div className="backdrop" />}
className="dialog"
>
<DialogHeading className="heading">Remove product</DialogHeading>
<DialogDescription className="description">
Are you sure you want to remove &quot;{removing}&quot; from your cart?
<div className="buttons">
<DialogDismiss onClick={confirmRemove} className="button danger">
Remove
<DialogDismiss autoFocus className="button secondary">
Cancel
</div>
</Dialog>
</>
);
}

Components

Identifying nested dialogs

There are three ways Ariakit can identify nested dialogs:

  1. 1

    They are nested in the React tree.

    <Dialog />
    </Dialog>
  2. 2

    They are appended to the body element after the parent dialog is opened.

    <Dialog />
  3. 3

    They are referenced in the getPersistentElements prop of the parent dialog.

    <Dialog getPersistentElements={() => document.querySelectorAll(".dialog")} />
    <Dialog className="dialog" />

In this example, we're using the second method. By passing the unmountOnHide prop to the nested dialog, it's rendered only when the dialog is open. This is the same strategy used by the Dialog module to support third-party dialogs and browser extensions that open popups, such as 1Password, Google Translate, and Grammarly.

This applies to all components that use Dialog under the hood, such as Combobox, Hovercard, Menu, Popover, Select, and Tooltip.

Follow updates

Join 1,000+ subscribers and receive monthly updates with the latest improvements on Examples.

Read latest issue

No Spam. Unsubscribe at any time.