Headless / Overlay

AlertDialog

AlertDialog is Dialog tuned for destructive confirmations. It carries the alertdialog role and ignores scrim clicks — the user must choose, not dismiss by reflex.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

Dialog with two defaults changed. Dialog.Root's role defaults to alertdialog, and Dialog.Content's dismissOnScrimClick defaults to false. Everything else — the parts, the props, the focus contract — is Dialog unchanged.

The reasoning is risk. A scrim click is an easy, ambiguous gesture; for "Delete account?" it must not be the answer. AlertDialog removes that path, leaving the explicit Confirm and Cancel actions.

Install

example.tsx
yarn add @usemotif/headless
example.tsx
import { AlertDialog } from '@usemotif/headless';

Anatomy

example.tsx
<AlertDialog.Root>
<AlertDialog.Trigger>
  <Button intent="danger">Delete</Button>
</AlertDialog.Trigger>
<AlertDialog.Content>
  <AlertDialog.Title>Delete account?</AlertDialog.Title>
  <AlertDialog.Description>This cannot be undone.</AlertDialog.Description>
  <AlertDialog.Close><Button>Cancel</Button></AlertDialog.Close>
  <Button intent="danger" onClick={confirmDelete}>Delete</Button>
</AlertDialog.Content>
</AlertDialog.Root>

API

AlertDialog exposes the same six parts as Dialog — Root, Trigger, Content, Title, Description, Close — with the same props. See the Dialog API for the full reference. The only differences are the defaults:

  • AlertDialog.Rootrole defaults to "alertdialog" instead of "dialog".
  • AlertDialog.ContentdismissOnScrimClick defaults to false instead of true.

Pass dismissOnScrimClick={true} to AlertDialog.Content to opt the scrim-click path back in.

Accessibility

role="alertdialog" tells assistive technology this dialog needs a response, not just attention — screen readers treat it with more urgency than a plain dialog. The focus contract is Dialog's: focus moves in on open, traps inside, and restores on close.

Keep both actions explicit. An AlertDialog should always have a clear Confirm and a clear Cancel — since the scrim no longer dismisses, those buttons are the only way out. Word the destructive one as the action ("Delete"), not a bare "OK".

Examples

A delete confirmation:

example.tsx
<AlertDialog.Root>
<AlertDialog.Trigger><Button intent="danger">Delete project</Button></AlertDialog.Trigger>
<AlertDialog.Content style={{ padding: 24, background: 'var(--colors-surface-base)' }}>
  <AlertDialog.Title>Delete this project?</AlertDialog.Title>
  <AlertDialog.Description>
    All files and history will be removed. This cannot be undone.
  </AlertDialog.Description>
  <HStack gap="$2">
    <AlertDialog.Close><Button intent="neutral">Cancel</Button></AlertDialog.Close>
    <Button intent="danger" onClick={deleteProject}>Delete project</Button>
  </HStack>
</AlertDialog.Content>
</AlertDialog.Root>