Alert Dialog
Modal confirmation that interrupts to ask one question before a destructive or irreversible action. For anything a user can browse, edit or dismiss casually, use Dialog.
Added 14 October 2026
Alert Dialog or Dialog?
- Alert Dialog — one question, two answers, real consequences. Delete a campaign, discard a draft, unlock a locked budget. The backdrop does not dismiss it, so the user has to answer rather than click away by accident.
- Dialog — everything else: forms, detail views, anything with a body to read or scroll. Dismissible by backdrop and Escape, ships a close button.
If the answer is “OK”, it is not an alert dialog — it is an Alert or a toast.
Dismissal contract
Identical in both frameworks, and deliberately not the same as Dialog:
- Backdrop click never dismisses. Base UI forces this for
alert-dialogmode; the Angular half sets brain’scloseOnOutsidePointerEvents: false. - Escape does dismiss. Base UI passes
escapeKeyunconditionally even in alert-dialog mode, so the Angular half overrides brain’s blanketdisableCloseto match rather than silently diverging.
Because Escape works, always keep a visible Cancel — Escape is not discoverable on its own, and it is the only keyboard exit besides the buttons you render. [a11y]
The action button closes
AlertDialogAction / hlmAlertDialogAction render on the primitive’s Close, so they close
the dialog on click — matching Base UI’s own documented pattern. Cancel does too.
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive" onClick={remove}>Delete</AlertDialogAction>
</AlertDialogFooter>
For an async confirm that must stay open until the mutation settles, take control of
open on the root and close it yourself when the request resolves; don’t use Action for
that case. Show the outcome afterwards — a confirmation that closes silently gives a screen
reader nothing to announce. [a11y]
Composition
Angular’s portal directive is mandatory — omit *hlmAlertDialogPortal and nothing
renders. React’s AlertDialogPortal is internal (rendered by AlertDialogContent); it is
exported but you should not need it.
<hlm-alert-dialog>
<button hlmAlertDialogTrigger hlmBtn variant="outline">Delete campaign</button>
<hlm-alert-dialog-content *hlmAlertDialogPortal>
<hlm-alert-dialog-header>
<h2 hlmAlertDialogTitle>Delete this campaign?</h2>
<p hlmAlertDialogDescription>This removes all of its line items.</p>
</hlm-alert-dialog-header>
<hlm-alert-dialog-footer>
<button hlmAlertDialogCancel>Cancel</button>
<button hlmAlertDialogAction variant="destructive">Delete</button>
</hlm-alert-dialog-footer>
</hlm-alert-dialog-content>
</hlm-alert-dialog>
Title and description are wired to the dialog’s aria-labelledby / aria-describedby
automatically in both frameworks — use the parts, don’t hand-roll headings outside them.
Use <h2> for the title (React’s primitive renders one; match it in Angular so the document
outline agrees).
Sizes and the media slot
size="default"— centred and 320px on mobile, left-aligned and 512px fromsm:up.size="sm"— stays compact at all widths, footer becomes a two-column grid. Keep those two labels short; the grid cells cannot grow.AlertDialogMedia/hlm-alert-dialog-mediaholds one icon above (mobile) or beside (desktop) the title. Colour the icon for the destructive path (text-destructive) — the muted well alone is a weak signal, and colour must not be the only one, so the title still has to say what will happen. [a11y]
Long bodies scroll (max-h-[85vh]), and the box keeps a 1rem gutter from the screen edges.
Rules
- One question per dialog. If you need two decisions, you need a different surface.
- Label the buttons with the action, not “Yes”/“No” — “Delete”, “Discard”, “Keep locked”. The user should be able to answer without re-reading the title.
- Destructive confirm gets
variant="destructive"; Cancel stays the defaultoutline. - Say what happens, including what does not: “Bookings already delivered stay in reporting” prevents the support ticket.
- Initial focus lands on the first tabbable control, which is Cancel (footer DOM order;
flex-col-reverseonly reorders it visually). That is the safe default for a destructive action — don’t reorder the DOM to make the destructive button first. [a11y]
Never
- An alert dialog for information — no decision, no dialog.
- An alert dialog chained to another alert dialog.
- A confirm button that leaves the dialog open with no feedback.