DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
PopoverHoverCardDropdown MenuContextMenuModalAlertDialogDrawerAction Sheet
AI agentsContributing
  1. Components
  2. AlertDialog

AlertDialog

Blocking confirmation dialog for destructive or critical actions. Built on Base UI's alert-dialog primitive with role="alertdialog" and a focus trap. A click outside never dismisses it — the user confirms, cancels, or presses Escape, which cancels.

$ pnpm add @tessinaui/ui

When to use

  • A decision the user must answer. The dialog blocks the page until they confirm or cancel; an outside click does nothing.
  • A destructive or irreversible action the user may not expect, such as deleting a project. Set intent="error" so the action is coloured and Cancel takes initial focus.
  • A critical warning before data is lost or a change overwrites other people's work (intent="warning").
  • Two actions, a confirm and a cancel. A third needs a reason you can state; past that the hierarchy stops reading and the footer crowds on a phone.
  • A title that states the outcome ("Delete this project?", not "Are you sure?"), a description with the plain consequence (what is lost, whether it can be undone), and an action labelled with the title's verb ("Delete").

When not to use

  • For a recoverable action. Do it, then offer a Toast with Undo. An alert in front of a reversible action trains people to dismiss alerts without reading them, which is what makes the irreversible one dangerous. The same tiering drives ToolCall's risk levels.
  • For forms, lists or rich content. Use Modal, where an outside click is an acceptable dismissal and the decision is not critical.
  • To inform without a decision. Use Alert inline in the page flow, or a Toast for a result that needs no reply.
  • When destruction is the point of the flow the user chose. Emptying a trash you deliberately opened confirms with an ordinary button; intent="error" is for a destructive outcome the user may not be expecting.
  • With "OK", "Yes" or "No" labels, or raw error text. They force the reader back up to the title, and a screen-reader user who lands on the button hears nothing useful. Say what happened and what to do; keep any error code out of the title.

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
import {
  AlertDialog,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogBody,
  AlertDialogFooter,
  AlertDialogAction,
  AlertDialogCancel,
} from "@tessinaui/ui";
example.tsx
<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 is responsive by default — full-width and stacked below sm, an end-aligned row from sm up. Pin either with layout="row" or layout="stacked".

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.

Scrollable body

Put a long list of what the action affects in AlertDialogBody. It scrolls between a pinned header and footer, so the decision stays in view however long the list is.

Loading

AlertDialogSkeleton mirrors the popup shape while content loads.

On-color

On a coloured Surface the trigger takes the plate's ink and its focus ring from context — no prop needed. The dialog stays a standard panel: a portal opens it above the plate rather than on it, so its title, description and actions read exactly as they do on the page.

API Reference

AlertDialog (root)

PropTypeDefaultDescription
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
showIntentBorderbooleanfalseWhen true, the popup border picks up the intent colour. Opt-in — neutral border by default
dir"ltr" | "rtl"inheritedReading direction. Unset, read off the trigger when the dialog opens
openboolean—Controlled open state
defaultOpenbooleanfalseUncontrolled initial state
onOpenChange(open, event) => void—Fired when open state changes
busybooleanfalseA confirmed action is running: the action shows a spinner, both buttons go inert and every close request — Escape included — is vetoed until it clears

AlertDialogContent

Inherits size, rounded, intent, showIntentBorder and dir from the root. Override any of them to scope the popup independently.

PropTypeDefaultDescription
initialFocus"auto" | "action" | "cancel" | RefObject"auto"What takes focus on open. "auto" is Cancel for error / warning and the action otherwise
finalFocusRefObjectthe triggerWhat takes focus on close. Set it when the confirmed action removes the trigger

AlertDialogHeader

PropTypeDefaultDescription
iconReactNodeintent iconCustom leading icon next to the title
showIconbooleantrueRender an icon — set to false for icon-less alerts

AlertDialogFooter

PropTypeDefaultDescription
layout"responsive" | "row" | "stacked""responsive"Stacked below sm and a row from sm up, always a row, or always stacked

AlertDialogAction, AlertDialogCancel

Both render a Button. Pass any Button prop (size, rounded, loading, leadingIcon, etc.). Both auto-close the dialog on click.

  • AlertDialogAction defaults to variant="primary" and inherits the dialog's intent.
  • AlertDialogCancel defaults to variant="outline" and intent="none".
  • An AlertDialogAction whose onClick returns a promise does not close on that click — pair it with busy and close the dialog yourself when the work settles.

AlertDialogTrigger, AlertDialogClose

Render a <button>. Use render={<CustomElement />} to swap in a Button or any element. Standard Base UI render-prop pattern.

AlertDialogSkeleton

PropTypeDefaultDescription
size"sm" | "md" | "lg" | "xl""md"Max-width, matching the dialog it stands in for
rounded"none" | "sm" | "md" | "lg" | "full""md"Corner radius of the panel and its bars
showIconbooleantrueRender the leading icon placeholder
linesnumber2Description lines under the title
actionsnumber2Buttons in the footer
labelstring"Loading"What assistive tech announces while the placeholder is up

Sub-components

AlertDialogTitle, AlertDialogDescription, AlertDialogBody, AlertDialogBackdrop — all accept className and any HTML props.

Notes

  • Built on @base-ui/react/alert-dialog, with W3C role="alertdialog" semantics.
  • The popup uses Base UI's data-[starting-style] and data-[ending-style] attributes for entry/exit animation.
  • The header icon background tints automatically per intent (bg-error-light etc.) for a soft callout.

Anatomy

example.tsx
<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

SizeMax 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.

ValueHeader iconDefault action buttonBorder with showIntentBorder
"none" (default)Infobg-primaryborder-border
"error"CircleXbg-errorborder-error
"warning"TriangleAlertbg-warningborder-warning
"success"CircleCheckbg-successborder-success
"info"Infobg-infoborder-info

Rounded

ValueCSS
"none"rounded-none
"sm"rounded-lg
"md" (default)rounded-xl
"lg"rounded-2xl
"full"rounded-3xl

Footer layout

AlertDialogFooter accepts layout:

  • "responsive" (default) — full-width and stacked below sm, a horizontal end-aligned row from sm up.
  • "row" — always horizontal, end-aligned.
  • "stacked" — always full-width and vertical.
example.tsx
<AlertDialogFooter layout="stacked">
  <AlertDialogCancel>Cancel</AlertDialogCancel>
  <AlertDialogAction>Power off</AlertDialogAction>
</AlertDialogFooter>

Write AlertDialogCancel first in every layout. A stacked footer reverses the visual order, so the confirm sits on top, closest to the title, while Cancel stays first in the tab sequence.

Dismissal behaviour

  • A click outside never dismisses. Unlike Modal, the backdrop does nothing — the alertdialog role asks for an explicit answer.
  • Escape closes, and counts as choosing Cancel.
  • AlertDialogAction, AlertDialogCancel and your own AlertDialogClose close on click.
  • While busy, every close request is vetoed — Escape included — until the action settles.

Controlled

example.tsx
const [open, setOpen] = React.useState(false);

<AlertDialog open={open} onOpenChange={setOpen}>
  …
</AlertDialog>

Custom close via AlertDialogClose

AlertDialogClose closes the dialog the moment it is clicked. Use it with render when the closing control is neither the confirm nor the cancel:

example.tsx
<AlertDialogClose render={<Button variant="ghost">Not now</Button>} />

It does not wait for a click handler. For an action that must finish before the dialog closes, keep AlertDialogAction and set busy on the root — see Async confirm.

Accessibility

Pattern: WAI-ARIA APG alertdialog.

  • The popup carries role="alertdialog" and aria-modal="true", is named by AlertDialogTitle and described by AlertDialogDescription. Rendering the description is not optional — Base UI wires aria-describedby from 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 with initialFocus on AlertDialogContent. The default is explicit rather than inherited from DOM order, so reordering the footer never moves focus onto the destructive button.
  • Escape closes, and must. It is 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.
  • Nothing is clipped on a short screen. At 30rem tall or less — a phone in landscape, or 400% zoom — the dialog scrolls as a whole, so its title and both actions stay reachable. A dialog with no AlertDialogBody scrolls that way whenever it is taller than the screen.
  • Outside clicks never dismiss. Base UI forces that for the alertdialog role; there is no prop to re-enable it.
  • While busy, both buttons are aria-disabled rather than disabled, so they stay in the tab sequence instead of vanishing mid-flight.
  • Cancel comes first in the tab order in every footer layout. A stacked footer reverses only the visual order, so the confirm sits on top while Cancel is still reached first.
  • Focus returns to the trigger on close. When the confirmed action destroys the trigger — deleting the row that opened the dialog — pass finalFocus so focus lands on a real element rather than body.

AT matrix: VoiceOver + Safari passed 2026-09-14 — Space on the trigger opens "web alert dialog", reading the title and description with focus on "Cancel, button"; Tab reaches "Delete, button" and wraps back; Escape returns to "Delete project, dialog pop up button". NVDA and JAWS UNTESTED (no Windows host, QA §5.6).

RTL

Pass dir="rtl" to the root. The header icon, text alignment, and footer button order all follow the reading direction. Tab order and initial focus do not change. Leave dir unset and the dialog follows the direction around the trigger that opened it; opened without a trigger, it follows the document.

ModalDrawer

On this page

When to useWhen not to useInstallationUsageExamplesDefaultIntentsSizesFooter layoutIntent borderDestructive vs non-destructiveAsync confirmScrollable bodyLoadingOn-colorAPI ReferenceAlertDialog (root)AlertDialogContentAlertDialogHeaderAlertDialogFooterAlertDialogAction, AlertDialogCancelAlertDialogTrigger, AlertDialogCloseAlertDialogSkeletonSub-componentsNotesAnatomySizeIntentRoundedFooter layoutDismissal behaviourControlledCustom close via AlertDialogCloseAccessibilityRTL