Skip to content
Navigation

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-dialog mode; the Angular half sets brain’s closeOnOutsidePointerEvents: false.
  • Escape does dismiss. Base UI passes escapeKey unconditionally even in alert-dialog mode, so the Angular half overrides brain’s blanket disableClose to 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 from sm: 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-media holds 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 default outline.
  • 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-reverse only 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.