A modal that interrupts the user with a binary choice — confirm the action or cancel. Distinct from a generic Dialog: pressing Escape or clicking the backdrop does not dismiss it. Use this for destructive or irreversible operations (delete, sign out, discard unsaved work).
AlertDialog is a compound — Root owns open state, Trigger opens it, Content is the panel (auto-portaled with its own overlay), Header stacks the Title and Description, Footer lays out the Cancel and Action pair.
import { AlertDialog } from "@craft-bits/core";<AlertDialog.Root> <AlertDialog.Trigger asChild> <button>Delete account</button> </AlertDialog.Trigger> <AlertDialog.Content> <AlertDialog.Header> <AlertDialog.Title>Delete this account?</AlertDialog.Title> <AlertDialog.Description> This permanently removes your data. There is no undo. </AlertDialog.Description> </AlertDialog.Header> <AlertDialog.Footer> <AlertDialog.Cancel>Cancel</AlertDialog.Cancel> <AlertDialog.Action onClick={() => deleteAccount()}> Delete </AlertDialog.Action> </AlertDialog.Footer> </AlertDialog.Content></AlertDialog.Root>
Drive the open state from your own store by passing open plus onOpenChange:
Alert vs. Dialog. A regular Dialog is informational — Escape and the backdrop dismiss it. AlertDialog is for blocking confirmations: Radix wires role="alertdialog", swallows the Escape-dismiss path, and gives initial focus to Cancel so a stray Enter press can't trigger a destructive action.
Compound parts. Root (state owner), Trigger (opens the dialog, accepts asChild), Overlay (backdrop scrim), Content (the panel; auto-wraps in Portal + Overlay unless you pass withPortal={false}), Header and Footer (layout helpers), Title and Description (Radix-wired aria-labelledby and aria-describedby), Action and Cancel (the two buttons).
Controlled or uncontrolled. Omit open and onOpenChange to let Radix manage state. Pass both to drive open/close from your own state — useful for triggering the dialog from a remote action (right-click menu, keyboard shortcut, async callback).
Title and Description are required. Radix-style alert dialogs must label themselves for assistive tech. Visually hide them with sr-only if your design has none — never omit them.
Action button placement. Footer reverses the column order on mobile so Action sits on top (thumb-first), then switches to a right-aligned row on sm: so it ends up on the right.
asChild everywhere. Trigger, Action, and Cancel all accept asChild — drop in your own Button primitive and the dialog's event handlers merge onto it via Radix's Slot.
Props
AlertDialog.Root
Prop
Type
Default
Description
open
boolean
—
Controlled open state. Pair with onOpenChange.
onOpenChange
(open: boolean) => void
—
Fires when the dialog opens or closes.
defaultOpen
boolean
false
Uncontrolled initial open state.
AlertDialog.Trigger
Prop
Type
Default
Description
asChild
boolean
false
Render trigger props onto the child element via Radix Slot.
className
string
—
Merged onto the rendered element.
AlertDialog.Content
Prop
Type
Default
Description
withPortal
boolean
true
Auto-wrap in Portal + Overlay. Set false to compose them yourself.
overlayClassName
string
—
Merged onto the auto-rendered overlay (only when withPortal is true).
className
string
—
Merged onto the rendered panel.
AlertDialog.Header / AlertDialog.Footer
Prop
Type
Default
Description
className
string
—
Merged onto the rendered <div>.
AlertDialog.Title / AlertDialog.Description
Prop
Type
Default
Description
className
string
—
Merged onto the rendered element.
AlertDialog.Action / AlertDialog.Cancel
Prop
Type
Default
Description
asChild
boolean
false
Render button props onto the child element via Radix Slot.
className
string
—
Merged onto the rendered button.
Accessibility
Radix renders the panel as role="alertdialog" — assistive tech announces it as a confirmation prompt rather than a generic dialog.
Title is wired as the panel's accessible name (aria-labelledby); Description is wired as its description (aria-describedby). Both are required — wrap them in sr-only if your visual design has none.
Initial focus lands on Cancel, not Action — destructive actions require a deliberate Tab plus Enter, never a stray Enter on whatever was focused before the dialog opened.
Tab cycles through Cancel and Action, trapping inside the panel until it closes.
Escape and backdrop clicks do not dismiss the dialog by design — only Cancel or Action closes it.
The trigger receives focus back when the dialog closes so keyboard users return to where they were.
Credits
Extracted from: algoflashcards (src/platform/ui/alert-dialog.tsx). The original wrapped each Action/Cancel in the project's Button component and bundled a custom Media slot; craft-bits drops the project-specific Button coupling, exposes plain buttons (consumers swap in their own via asChild), and rewires the styling to cb-* tokens.