Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Alert Dialog

A modal confirmation dialog with four semantic intents, backdrop lock, and deliberate cancel/confirm actions.

Status
stable
Since
0.1.0
Accessibility pattern
APG alert dialog (modal)

Last updated

Install

Add Alert Dialog from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/alert-dialog

The same command installs the registry items it composes: @vegastack/button, @vegastack/use-modal-inert.

Usage

import {
  AlertDialog,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogFooter,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogAction,
  AlertDialogCancel,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";

<AlertDialog>
  <AlertDialogTrigger
    render={
      <Button variant="outline" tone="destructive">
        Delete project
      </Button>
    }
  />
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Delete project?</AlertDialogTitle>
      <AlertDialogDescription>
        This action cannot be undone.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction intent="destructive" onClick={handleDelete}>
        Delete project
      </AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>;

Unlike Dialog, an alert dialog is not dismissable by clicking the backdrop — the user can cancel with AlertDialogCancel or Esc, or confirm with AlertDialogAction. Reach for it for destructive or irreversible confirmations; for everything else use Dialog.

Anatomy

AlertDialog is a compound component built on Base UI's AlertDialog. Compose the parts inside the root:

AlertDialog
AlertDialogAction — data-slot="alert-dialog-action"
AlertDialogCancel — data-slot="alert-dialog-cancel"
AlertDialogContent — data-slot="alert-dialog-backdrop" | "alert-dialog-content" | "alert-dialog-viewport"
AlertDialogDescription — data-slot="alert-dialog-description"
AlertDialogFooter — data-slot="alert-dialog-footer"
AlertDialogHeader — data-slot="alert-dialog-header"
AlertDialogTitle — data-slot="alert-dialog-title"
AlertDialogTrigger — data-slot="alert-dialog-trigger"
<AlertDialog>
  <AlertDialogTrigger render={<Button>Delete</Button>} />
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Title</AlertDialogTitle>
      <AlertDialogDescription>
        Supporting description text.
      </AlertDialogDescription>
    </AlertDialogHeader>
    {/* optional body content */}
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction intent="destructive">Confirm</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>
  • AlertDialog — the root; owns open/close state (open / defaultOpen / onOpenChange). Always modal — focus is trapped, page scroll is locked, and the backdrop does not dismiss.
  • AlertDialogTrigger — the control that opens the dialog (data-slot="alert-dialog-trigger"). Pass render to compose it with a Button.
  • AlertDialogContent — the centered popup (data-slot="alert-dialog-content"). Composes Base UI's Portal + Backdrop + Viewport + Popup and animates enter/exit. No top-right close button, and no intent — the confirm button owns that.
  • AlertDialogHeader — groups the title and description (data-slot="alert-dialog-header").
  • AlertDialogTitle — the dialog's accessible name (data-slot="alert-dialog-title", <h2>). Wired to the popup via aria-labelledby.
  • AlertDialogDescription — supporting text (data-slot="alert-dialog-description", <p>). Wired via aria-describedby.
  • AlertDialogFooter — the action row at the bottom (data-slot="alert-dialog-footer").
  • AlertDialogCancel — closes without confirming (data-slot="alert-dialog-cancel"). Neutral outline tint.
  • AlertDialogAction — the confirm button (data-slot="alert-dialog-action"). Closes the dialog; wire confirm work through onClick. Carries the intent tint.

Not backdrop-dismissable: there is intentionally no close (X) button and backdrop clicks do not dismiss. The user can cancel with Cancel or Esc, or choose the confirm Action — that's what separates an alert dialog from a regular Dialog.

Examples

Intents

Set intent on AlertDialogAction to tint the confirm button: default, destructive, success, and warning.

AlertDialogContent deliberately has no intent. It used to accept one that wrote a data-intent attribute and nothing else, while the visible colour came from AlertDialogAction — two props for one concept, one of them inert. AlertDialogAction intent is the single owner; it maps to the shared Button matrix (variant="outline" plus the matching tone), so a destructive confirm is an outline button, never a solid red one.

Scrollable content

When the body is long, drop a scrolling region (overflow-y-auto) between the header and footer. The popup caps its own height (max-h-[calc(100dvh-…)]) and keeps the header and footer pinned while the middle scrolls.

API Reference

AlertDialogContent

AlertDialogContent adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

Data attributes and CSS variables on AlertDialogContent

AttributeValues
data-slot"alert-dialog-backdrop" | "alert-dialog-content" | "alert-dialog-viewport"

AlertDialogAction

PropTypeDefaultDescription
intentAlertDialogIntent"default"Semantic tint of the confirm button — the single owner of the confirmation's severity.
loadingbooleanfalseShows a spinner and marks the confirm button busy while an async confirm is pending — wired straight through to the underlying Button's loading prop (spinner, aria-busy, and a real disabled control). Because the rendered button becomes genuinely disabled, a click while loading never reaches Base UI's Close click handler, so the dialog stays open until you flip loading back to false (typically in the onClick handler's finally).

Data attributes and CSS variables on AlertDialogAction

AttributeValues
data-intentmirrors a prop or state value
data-slot"alert-dialog-action"

AlertDialog, AlertDialogTrigger, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, and AlertDialogCancel add no props of their own — they accept everything the underlying Base UI part or element accepts (className, render, ARIA attributes, event handlers, …).

Accessibility

  • The popup renders with role="alertdialog" + aria-modal="true". AlertDialogTitle and AlertDialogDescription are auto-wired as its aria-labelledby / aria-describedby — always include a title.
  • Focus is trapped inside the popup while open and restored to the trigger on close. VegaStack mirrors Base UI's live outside-tree markers to native inert, shared with Dialog and Sheet through reference-counted ownership, so focus cannot restart in page chrome after a transient <body> hand-off. Page scroll is locked.
  • The backdrop is non-dismissable by design — closing requires AlertDialogCancel, AlertDialogAction, or Esc as a cancel request, so a confirmation is never dismissed by accident.
  • Every interactive control is a Button and follows Button's focus treatment: each keeps the global 2px :focus-visible ring (outline-ring), and the bordered AlertDialogCancel (outline variant) additionally re-colors its border with the ring token (focus-visible:border-ring/(--alpha-tint-border)) — never outline: none.
KeyAction
Enter / SpaceActivate the focused trigger to open the dialog.
Tab / Shift + TabMove focus between Cancel and Action — trapped within the open dialog.
EscClose the dialog (treated as cancel).
ContractStates tested
Behaviourdefault, disabled, error, loading, open, success
Accessibilitybusy, described, focus-visible, labeled, semantic-html
Visualdefault, loading, error, success

Do / Don't

Do
Use an AlertDialog for destructive or irreversible confirmations, match the Action intent to the consequence, and put Cancel before Action in the footer.
Don't
Use an AlertDialog for routine, dismissable content — use a Dialog instead. Don't bury the confirm action behind a neutral tint when it's destructive.

On this page