Alert Dialog
A confirmation that cannot be dismissed by accident.
Installation
npx shadcn@latest add https://qure-ui.qure.ai/r/alert-dialog.jsonUsage
import {
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader,
AlertDialogTitle, AlertDialogTrigger,
} from '@/components/ui/alert-dialog'<AlertDialog>
<AlertDialogTrigger render={<Button variant="destructive">Delete study</Button>} />
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete CT Thorax, ACC-100482?</AlertDialogTitle>
<AlertDialogDescription>The study and its 214 images are removed.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel render={<Button variant="tertiary">Keep study</Button>} />
<AlertDialogAction render={<Button variant="destructive">Delete</Button>} />
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>Alert Dialog or Dialog
They look nearly the same on purpose and they behave differently on purpose.
| Dialog | Alert Dialog | |
|---|---|---|
| Click the backdrop | closes | nothing |
| Click outside | closes | nothing |
| Escape | closes | closes |
| Close button in the corner | yes | no |
Base UI enforces the difference in the type: modal and disablePointerDismissal are not props
of AlertDialog.Root, so pointer dismissal cannot be put back. That is the entire reason to
reach for it — a misplaced click beside a dialog should not count as an answer.
Escape does still close an alert dialog. That surprises people who expect it to be sealed,
and it is correct: the ARIA authoring practices require Escape on alertdialog, and a modal
with no keyboard exit is a trap. Treat Escape as a synonym for the cancel button, and make
sure cancelling is always the safe outcome.
Use it when the answer is destructive or irreversible and the user has to say so; use
Dialog for everything else, because a dialog that cannot be dismissed is a rude way to ask an
ordinary question. A form is not an alert dialog. Nor is a notice with one OK button — that is a
toast.
Composition
<AlertDialog> {/* owns the open state */}
<AlertDialogTrigger /> {/* renders your Button */}
<AlertDialogContent> {/* portal + backdrop + popup */}
<AlertDialogHeader> {/* title and description, scrollable */}
<AlertDialogTitle /> {/* labels the dialog. Required. */}
<AlertDialogDescription /> {/* describes it. State the consequence. */}
</AlertDialogHeader>
<AlertDialogFooter> {/* the action row, with an optional note */}
<AlertDialogCancel /> {/* the way out. Always present. */}
<AlertDialogAction /> {/* the destructive answer. */}
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>Title and Description are not decoration: Base UI wires them to the popup's
aria-labelledby and aria-describedby, so leaving the description out means a screen-reader
user hears the question and not the consequence.
Both Cancel and Action are Close underneath — they close on click. For an action that has
to wait on the server, use a plain Button instead and close it yourself; see below.
Delete a study
Name the thing in the title. "Are you sure?" is not a question anyone can answer — "Delete CT Thorax, ACC-100482?" is. Say what survives as well as what goes.
With a note
Figma's Action-bar (node 3334:7269) has a note slot to the left of the buttons; note on the
footer fills it. Use it for a consequence too small to be the description and too important to
leave out — "recorded against your account", "the referrer is notified".
Focus on the safe option
initialFocus takes a ref. Base UI would otherwise focus the first tabbable element, which is
the cancel button here by luck rather than by design; saying it keeps it true if the order
changes. A destructive dialog where Enter fires the destructive button is a trap for anyone who
opened it by keyboard.
Controlled, with a pending action
Drive it with open and onOpenChange when the trigger lives somewhere else — a menu item, a
row action, a keyboard shortcut. The confirm button here is a plain Button, not
AlertDialogAction, because Action closes on click and this one must stay open until the
request returns.
Under prefers-reduced-motion the transitions are dropped, not shortened. A confirmation is
not the place to make someone wait out an animation they asked not to see.
API Reference
Prop
Type
The rest is Base UI's Alert Dialog — minus
modal and disablePointerDismissal, which it does not have.