Dialog
A modal, built on the native <dialog> element — so focus trapping, Escape, inert background and the backdrop are the browser's job, not ours.
Confirm before something irreversible
Root can restart production. Today its approve button fires the moment it is clicked; this is what should be in front of it.
The confirm button carries the verb, never “OK”. Someone who opened this by accident reads the button, not the paragraph — and “Deploy to production” stops a hand that “OK” would not.
A plain dialog
For anything that is not a confirmation.
Guidance
Use it when
- Confirming something irreversible or outward-facing.
- A short detail view that would lose the page's scroll position if it were a route.
Reach for something else when
- A form of more than a few fields. That is a page — a modal cannot be linked to or reloaded.
- Confirming something reversible. A confirm on every action trains people to click through them.
- Stacking a dialog on a dialog.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | — | Controlled. The effect calls showModal()/close() to match. |
| onClose | () => void | — | Fires for Escape and the backdrop too, so parent state can never drift from what the browser shows. |
| title | string | — | Labels the dialog for assistive tech. |
| confirmLabel | string | — | ConfirmDialog only. The verb — not “OK”. |
| tone | "danger" | "default" | "danger" | ConfirmDialog only. Styles the confirm button. |
| loading | boolean | — | ConfirmDialog only. Spinner on confirm, cancel disabled. |
Why native
showModal() gives focus trapping, Escape-to-close, inert on the rest of the page and a ::backdrop — all handled by the browser. A hand-rolled div-and-overlay gets those wrong more often than right, and the failures are invisible until someone tries it with a keyboard.