Ariakit
/

Nested Dialog

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

import {
} from "@ariakit/react";
import { useState } from "react";
import "./style.css";
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.

Stay tuned

Join 1,000+ subscribers and receive monthly tips & updates on new Ariakit content.

No spam. Unsubscribe anytime. Read latest issue