Alert Dialog
Interrupt only for a meaningful consequential choice with clear consequences, a safe default and an enabled return.
Use a consequential confirmation
Use AlertDialog for a brief important message requiring a response. Do not confirm every harmless toggle or use repeated confirmation as a substitute for understandable actions and undo. Use a regular Dialog for editing and an inline Alert for contextual guidance.
Describe the exact object, consequence and recovery. A button labelled Confirm is usually less useful than the actual action. In the local lab, removal affects no database, source or recording; a persistent restore control remains available.
Anatomy and specifications
| Part | Contract |
|---|---|
| Popup | Named alertdialog with connected description; always modal |
| Backdrop | Fixed 50% black dimming; outside-pointer dismissal blocked by the primitive |
| Surface | 24px padding; 2px border; --radius-lg; --shadow-xl; internal scroll |
| Size | sm 24rem / md 32rem / lg 42rem; md default; viewport minus 2rem width |
| Focus | Least destructive meaningful action first; a result that disables the trigger needs an alternate final focus |
| Actions | Explicit safe cancel and clearly named consequence; wrap at narrow widths |
Working confirmation and independent restore
Local example draft is present.
'use client';
import { useRef, useState } from 'react';
import { AlertDialog, AlertDialogBackdrop, AlertDialogClose, AlertDialogDescription, AlertDialogPopup, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Button } from '@mobtranslate/ui';
export function ConfirmationContainmentExample() {
const [open, setOpen] = useState(false);
const [exists, setExists] = useState(true);
const [result, setResult] = useState('Local example draft is present.');
const cancel = useRef<HTMLButtonElement>(null);
const restore = useRef<HTMLElement>(null);
return <div data-confirmation-lab className="space-y-4">
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger disabled={!exists} render={<Button type="button" variant="destructive" />}>Remove local example</AlertDialogTrigger>
<AlertDialogPortal><AlertDialogBackdrop /><AlertDialogPopup initialFocus={cancel} finalFocus={() => exists ? true : restore.current} className={state => state.open ? 'local-confirmation-open' : ''}>
<AlertDialogTitle>Remove this local example?</AlertDialogTitle><AlertDialogDescription>This changes only the example on this page. A separate restore action remains available. No source, recording or database row will be deleted.</AlertDialogDescription>
<div className="flex flex-wrap gap-3 mt-6"><AlertDialogClose ref={cancel} render={<Button type="button" variant="outline" />}>Keep local example</AlertDialogClose><Button type="button" variant="destructive" onClick={() => { setExists(false); setResult('Local example removed. Use Restore local example to recover.'); setOpen(false); }}>Confirm local removal</Button></div>
</AlertDialogPopup></AlertDialogPortal>
</AlertDialog>
<Button ref={restore} type="button" variant="outline" aria-disabled={exists || undefined} onClick={() => { if (!exists) { setExists(true); setResult('Local example restored.'); } }}>Restore local example</Button>
<p role="status">{result}</p>
</div>;
}
Root API
| Root prop | Installed contract |
|---|---|
| open / defaultOpen | Controlled boolean / initial uncontrolled boolean; default false. |
| onOpenChange | (open, eventDetails); details.reason describes cause; details.cancel() can reject a change. |
| onOpenChangeComplete | Called after open/close animations finish; distinct from a submitted task succeeding. |
| modal | Dialog/Drawer: true default; false or trap-focus alternatives. AlertDialog is always modal. |
| disablePointerDismissal | Dialog/Drawer: false default. AlertDialog does not expose this prop and prevents outside-pointer dismissal. |
| actionsRef | Imperative close/unmount lifecycle; do not hold invisible inert content indefinitely. |
| handle / triggerId / defaultTriggerId | Installed primitive trigger association/payload mechanisms; imperative triggers need a meaningful final focus destination. |
| children | Parts or installed payload render function. Root does not render its own DOM node. |
Parts API
Exports: AlertDialog, AlertDialogTrigger, AlertDialogClose, AlertDialogPortal, AlertDialogBackdrop, AlertDialogPopup, AlertDialogTitle, AlertDialogDescription and AlertDialogPopupProps. Popup adds size=sm|md|lg, md default. AlertDialogClose is a close control, not an automatic destructive action. There is no exported automatic confirmation/request primitive.
| Part / prop | Contract |
|---|---|
| Trigger | Installed button/render props, disabled, nativeButton, trigger payload/association; button ref. Use render={<Button type="button" />} to compose one control. |
| Close | Installed button/render props and button ref; closes the surface, not an application request. |
| Portal | Mounts the surface outside local layout; preserve theme variables through its actual container. keepMounted retains the subtree, not an open state. |
| Backdrop | Div/render props, className string or state callback, style and div ref; visible dimming belongs to modality. |
| Popup | Div/render props and div ref; initialFocus and finalFocus accept boolean, element ref or interaction-type callback. |
| Title | Installed heading/render props and heading ref; supplies the visible dialog name. |
| Description | Installed paragraph/render props and paragraph ref; supplies a concise purpose/message. |
| className / render / native props | Styled parts retain primitive state callbacks. Custom render must preserve forwarded props, semantics and refs. |
Focus, keyboard and dismissal
- Choose initial focus deliberately: a labelled field for a small edit, a focusable heading for long structured content, or the least destructive action for a consequential choice.
- Modal Tab/Shift+Tab remain inside; Escape closes when application policy permits. Always include a visible named close or cancel action.
- Return to the invoking control, or a meaningful enabled alternative when that control is removed or disabled by the result.
- Do not make long semantic content one flattened description. Use a concise description and retain headings, lists and paragraphs for normal reading.
- A pending operation can prevent accidental dismissal only when a real cancellation/recovery path remains. Closing the surface is not cancelling a network request.
- Non-modal use needs an explicit workflow and keyboard route between the surface and page. Do not claim modal blocking for modal=false or pointer blocking for trap-focus. Styled popup wrappers expose aria-modal only for fully modal roots; omit a dimming backdrop for non-modal/focus-only layouts.
Consequences, cancellation and failures
- Cancel, Escape or merely opening the confirmation must not perform its consequential action. Outside-pointer dismissal is disabled for this primitive.
- After local removal, the old trigger is disabled and finalFocus points to the enabled restore action. Keeping focus on a disabled or removed trigger is a failure.
- For irreversible real work, choose the least destructive initial action and explain why recovery is unavailable. Never claim that a dialog itself supplies authorization.
- If confirmation starts an asynchronous request, retain pending/failure/retry state and reconcile the actual persisted outcome before announcing success.
- Avoid duplicate or generic action names and urgent dialogs for routine transient feedback.
Verification and release scope
- Verify the actual title, description relationships, focus destination, keyboard operation and task result in the consuming application.
- Exercise narrow and short viewports, both themes, long labels, text spacing, reduced motion and forced colors. Reach the last action by scrolling and keyboard.
- For overlays, check background interaction, scroll locking and each permitted dismissal path. A role or aria-modal attribute alone does not prove modal behavior.
- Use a named screen reader and browser for spoken output. Local examples and automated DOM checks do not prove announcements or a persisted backend result.
- Retain source/review/permission evidence. A container or confirmation cannot grant authority or establish that a request succeeded.