AlertDialog
Blocking confirmation dialog for destructive or critical actions. Built on Base UI's alert-dialog primitive with role="alertdialog", focus trap, and explicit-action-only dismissal — the user must choose Confirm or Cancel.
Playground
Installation
pnpm add @tessinaui/uiUsage
import {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogBody,
AlertDialogFooter,
AlertDialogAction,
AlertDialogCancel,
} from "@tessinaui/ui";<AlertDialog intent="error">
<AlertDialogTrigger render={<Button intent="error">Delete</Button>} />
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete this project?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>Examples
Default
A destructive confirmation with the error intent and an icon trigger.
Intents
intent drives the header icon and the default action-button colour — error, warning, success, and info.
Sizes
Four max-widths — sm, md, lg, and xl — to fit the message length.
Footer layout
AlertDialogFooter lays buttons out as an end-aligned row or a full-width stacked column.
Intent border
Opt in to a colour-matched popup border with showIntentBorder.
Destructive vs non-destructive
Intent decides more than colour: a destructive dialog focuses Cancel on open, per the APG rule to focus the least destructive action when undoing is impossible. A non-destructive one focuses the confirm instead.
Async confirm
Set busy while the mutation runs. The dialog stays open, the action shows a spinner, both buttons go inert, and Escape is vetoed — without the key ever being disabled.
Loading
AlertDialogSkeleton mirrors the popup shape while content loads.
Accessibility
Pattern: WAI-ARIA APG alertdialog.
- The popup carries
role="alertdialog"andaria-modal="true", is named byAlertDialogTitleand described byAlertDialogDescription. Rendering the description is not optional — Base UI wiresaria-describedbyfrom that part, so omitting it silently drops an attribute the pattern requires. - Initial focus defaults to Cancel for
intent="error"/"warning"and to the action otherwise; override withinitialFocusonAlertDialogContent. The surveyed systems split 4–4 on the general default, so it is explicit here rather than inherited from DOM order. - Escape closes, and must. Nine of eleven surveyed systems require it, and Spectrum defines it as equivalent to choosing Cancel. To hold the dialog open during an in-flight action use
busy, which vetoes the close in the handler — never disable the key. - Outside clicks never dismiss. Base UI forces that for the
alertdialogrole; there is no prop to re-enable it. - While
busy, both buttons arearia-disabledrather thandisabled, so they stay in the tab sequence instead of vanishing mid-flight. - Focus returns to the trigger on close. When the confirmed action destroys the trigger — deleting the row that opened the dialog — pass
finalFocusso focus lands on a real element rather thanbody.
AT matrix: VoiceOver + Safari — not yet run for this component. NVDA and JAWS UNTESTED (no Windows host, QA §5.6). The badge is beta for that reason, not because the API is unsettled.
When to use AlertDialog vs. Modal vs. Alert
| Use AlertDialog when… | Use Modal when… | Use Alert when… |
|---|---|---|
| The user must explicitly confirm or cancel | The dialog holds form fields, lists, or rich content | You only need to inform the user inline |
| The action is destructive, irreversible, or critical | Outside-click and Escape are acceptable dismissals | The message can be ignored without consequence |
You need stronger accessibility semantics (role="alertdialog") | The decision isn't critical | The notification appears in the page flow, not as an overlay |
Anatomy
<AlertDialog>
<AlertDialogTrigger>…</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle />
<AlertDialogDescription />
</AlertDialogHeader>
<AlertDialogBody /> {/* optional */}
<AlertDialogFooter>
<AlertDialogCancel />
<AlertDialogAction />
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>AlertDialogHeader automatically renders the icon for the current intent (or your custom icon). AlertDialogAction is intent-aware — it inherits the dialog's intent so destructive confirmations are coloured correctly without extra props.
Size
| Size | Max width |
|---|---|
"sm" | max-w-sm (24rem) |
"md" (default) | max-w-md (28rem) |
"lg" | max-w-lg (32rem) |
"xl" | max-w-xl (36rem) |
Intent
intent drives the header icon (auto) and the default colour of AlertDialogAction. The popup border stays neutral by default — opt in to a coloured border with showIntentBorder when the surrounding context doesn't already convey urgency.
| Value | Header icon | Default action button | Border with showIntentBorder |
|---|---|---|---|
"none" (default) | Info | bg-primary | border-border |
"error" | CircleX | bg-error | border-error |
"warning" | TriangleAlert | bg-warning | border-warning |
"success" | CircleCheck | bg-success | border-success |
"info" | Info | bg-info | border-info |
Rounded
| Value | CSS |
|---|---|
"none" | rounded-none |
"sm" | rounded-lg |
"md" (default) | rounded-xl |
"lg" | rounded-2xl |
"full" | rounded-3xl |
Footer layout
AlertDialogFooter accepts layout:
"row"(default) — horizontal, end-aligned. Standard desktop pattern."stacked"— full-width buttons stacked vertically. iOS / mobile style.
<AlertDialogFooter layout="stacked">
<AlertDialogAction>Power off</AlertDialogAction>
<AlertDialogCancel>Cancel</AlertDialogCancel>
</AlertDialogFooter>When layout="stacked", place AlertDialogAction first — flex-col-reverse puts it on top so the destructive action sits closest to the title.
Dismissal behaviour
Unlike Modal, the alert dialog cannot be dismissed by:
- Clicking outside the popup
- Pressing Escape
The user must choose AlertDialogAction, AlertDialogCancel, or your own AlertDialogClose. This is the W3C-recommended pattern for role="alertdialog".
Controlled
const [open, setOpen] = React.useState(false);
<AlertDialog open={open} onOpenChange={setOpen}>
…
</AlertDialog>Custom action via AlertDialogClose
When you need full control over the button (e.g., async confirmation, loading state), use AlertDialogClose directly with render:
<AlertDialogClose
render={
<Button onClick={async () => {
await deleteProject();
// dialog closes automatically after the click handler resolves
}}>
Delete forever
</Button>
}
/>Accessibility
- Renders with
role="alertdialog"so screen readers treat it as a critical interruption. AlertDialogTitleis wired toaria-labelledby,AlertDialogDescriptiontoaria-describedby.- Focus traps inside the popup. On open, focus moves to the first focusable element (typically
AlertDialogCancel). - On close, focus returns to the trigger.
- The popup is not dismissible by outside click or Escape — both gestures are intentionally swallowed.
- Use
intent="error"for destructive confirmations; the semantic colour, header icon, and action button intent all update so assistive tech and sighted users get the same signal.
RTL
Pass dir="rtl" to the root. The header icon, title, footer button order, and stacked layout all follow the reading direction.
<AlertDialog dir="rtl" intent="error">
<AlertDialogTrigger render={<Button intent="error">حذف</Button>} />
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>هل أنت متأكد من الحذف؟</AlertDialogTitle>
<AlertDialogDescription>لا يمكن التراجع عن هذا الإجراء.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>إلغاء</AlertDialogCancel>
<AlertDialogAction>حذف</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>API Reference
AlertDialog (root)
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "lg" | "xl" | "md" | Max-width of the popup |
rounded | "none" | "sm" | "md" | "lg" | "full" | "md" | Corner radius — also inherited by AlertDialogAction and AlertDialogCancel |
intent | "none" | "error" | "warning" | "success" | "info" | "none" | Header icon and default action-button intent |
showIntentBorder | boolean | false | When true, the popup border picks up the intent colour. Opt-in — neutral border by default |
dir | "ltr" | "rtl" | "ltr" | Reading direction |
open | boolean | — | Controlled open state |
defaultOpen | boolean | false | Uncontrolled initial state |
onOpenChange | (open, event) => void | — | Fired when open state changes |
AlertDialogContent
Inherits size, rounded, intent, and dir from the root. Override any of them to scope the popup independently.
AlertDialogHeader
| Prop | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | intent icon | Custom leading icon next to the title |
showIcon | boolean | true | Render an icon — set to false for icon-less alerts |
AlertDialogFooter
| Prop | Type | Default | Description |
|---|---|---|---|
layout | "row" | "stacked" | "row" | Horizontal end-aligned, or full-width stacked vertically |
AlertDialogAction, AlertDialogCancel
Both render a Button. Pass any Button prop (size, rounded, loading, leadingIcon, etc.). Both auto-close the dialog on click.
AlertDialogActiondefaults tovariant="primary"and inherits the dialog'sintent.AlertDialogCanceldefaults tovariant="outline"andintent="none".
AlertDialogTrigger, AlertDialogClose
Render a <button>. Use render={<CustomElement />} to swap in a Button or any element. Standard Base UI render-prop pattern.
Sub-components
AlertDialogTitle, AlertDialogDescription, AlertDialogBody, AlertDialogBackdrop — all accept className and any HTML props.
Notes
- Built on
@base-ui/react/alert-dialog, with W3Crole="alertdialog"semantics. - The popup uses Base UI's
data-[starting-style]anddata-[ending-style]attributes for entry/exit animation. - The header icon background tints automatically per intent (
bg-error-lightetc.) for a soft callout.