Alert Dialog
Primitive

Alert Dialog

Modal alert dialog behavior for urgent confirmations and decisions that require an answer before the user can continue.

Live behavior

Alert Dialog in motion

Interactive
Atom behavior · App-owned appearance

Open the decision, then cancel or confirm it with the keyboard.

waiting for input

When to Use

Use AlertDialog when the user must stop and make an important choice, such as confirming a destructive action or acknowledging a serious consequence. Use Dialog for ordinary forms, settings, and information that does not require an urgent decision.

Features

The behavior Atom owns before your product adds appearance.

  • Forces role="alertdialog" on Content.
  • Prevents backdrop-click dismissal by design.
  • Supports controlled and uncontrolled open state.
  • Autofocuses Cancel by default so the safer choice receives initial focus.
  • Reports action, cancel, and Escape close reasons.
  • Traps focus, restores focus, handles Escape, and locks document scrolling.
  • Uses stack-aware Escape dismissal so nested overlays close first.
  • Supports optional portals and keep-mounted content.

Import

tsx
import { AlertDialog } from "@flowstack-ui/atom";

Anatomy

tsx
<AlertDialog.Root>
  <AlertDialog.Trigger />
  <AlertDialog.Portal>
    <AlertDialog.Overlay />
    <AlertDialog.Content>
      <AlertDialog.Title />
      <AlertDialog.Description />
      <AlertDialog.Cancel />
      <AlertDialog.Action />
    </AlertDialog.Content>
  </AlertDialog.Portal>
</AlertDialog.Root>

API Reference

Root

Owns the alert dialog's open state and close policy. Root renders no DOM element; it provides state and generated IDs to the other parts.

PropTypeDefault
openboolean-
defaultOpenbooleanfalse
onOpenChange(open: boolean, reason?: AlertDialogCloseReason) => void-
closeOnEscapebooleantrue
disabledbooleanfalse
keepMountedbooleanfalse

Backdrop dismissal is always disabled and is not exposed as a Root prop.

Trigger

Opens the alert dialog and stores the element used for focus restoration. It renders a native button by default and accepts native button props.

PropTypeDefault
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
role"button" for custom renders
aria-haspopup"dialog"
aria-expanded"true" | "false"
aria-controlsContent ID while open
aria-disabled"true" when Root is disabled
Data attributeValues
[data-slot]"alert-dialog-trigger"
[data-state]"open" | "closed"
[data-disabled]Present when Root is disabled

Portal

Moves its children to document.body after mounting, or to a supplied container. Set disabled to render the children in place.

PropTypeDefault
containerHTMLElement | nulldocument.body
disabledbooleanfalse

The container must be an HTMLElement in the current document. ShadowRoot, DocumentFragment, and cross-document containers are unsupported. Atom keeps the ancestor paths to separate Overlay and Content portals, inline Content, nested same-document containers, and registered branches active while making background subtrees inert. Global Toast/live-region containers are background unless they are within an owned path or registered with Modal.Branch.

Portal renders no wrapper element.

Overlay

Renders the assistive-technology-hidden backdrop while the alert dialog is present. Clicking it never closes an AlertDialog. Overlay and Content must remain siblings; Atom rejects Content nested beneath the Overlay's aria-hidden subtree. Separate portals are valid when the committed Content DOM is outside Overlay.

PropTypeDefault
disabledbooleanfalse
ARIA attributeValues
aria-hidden"true"
Data attributeValues
[data-slot]"alert-dialog-overlay"
[data-state]"open" | "closed"
[data-positioned]Present after the first positioning frame

Content

Renders the modal alert dialog panel, traps focus while open, restores focus on close, and supplies the generated title and description relationships.

PropTypeDefault
aria-labelstring-
aria-labelledbystringgenerated from Title when present
aria-describedbystring | undefinedgenerated from Description when present
ariaLabelstringcompatibility fallback
initialFocusModalFocusTarget<ModalInitialFocusDetails>Cancel through native autoFocus
finalFocusModalFocusTarget<ModalFinalFocusDetails>prior focus, then Trigger
ARIA attributeValues
role"alertdialog"
aria-modal"true" while open
aria-hidden"true" while retained only for exit presence
inertPresent while retained only for exit presence
aria-labelledbyExplicit native value, otherwise registered Title ID
aria-labelExplicit native value, otherwise ariaLabel compatibility value
aria-describedbyExplicit native value, otherwise registered Description ID
Data attributeValues
[data-slot]"alert-dialog-content"
[data-state]"open" | "closed"
[data-positioned]Present after the first positioning frame

Exit-animated Content retained after close immediately loses aria-modal and becomes inert and accessibility-hidden while the visual exit completes. Background isolation, focus ownership, and active scroll containment end when open becomes false.

Title

Renders the visible heading that names Content. It receives the generated ID referenced by aria-labelledby and renders an h2 by default.

PropTypeDefault
as"h1" | "h2" | "h3" | "h4" | "h5" | "h6""h2"
Data attributeValues
[data-slot]"alert-dialog-title"

Description

Renders a p that explains the consequence or decision. It receives the generated ID referenced by Content's aria-describedby.

Alert dialogs require an accessible description. Atom warns during development after registration settles when neither Description nor native aria-describedby is present. Native ARIA is preferred and takes precedence; ariaLabel remains supported only for compatibility.

Consumer-owned third-party portals must either wrap their top-level element in <Modal.Branch asChild> or target the AlertDialog Content element as their portal container. Nested modal layers suspend the parent while the child is topmost, including focus trapping, Escape handling, and scroll containment.

Cancel's native autoFocus remains the safe initial-focus default for keyboard and pointer opening, including touch. An explicit initialFocus target takes precedence. Use finalFocus for a different post-decision workflow target. Its details describe the closing interaction and include actionClick, cancelClick, or escapeKeyDown as applicable.

Data attributeValues
[data-slot]"alert-dialog-description"

Cancel

Renders the safer control, closes with reason: "cancelClick", and receives initial focus by default. It renders a native button unless composed.

PropTypeDefault
asChildbooleanfalse
renderRenderProp-
autoFocusbooleantrue
Data attributeValues
[data-slot]"alert-dialog-cancel"

Action

Renders the confirmation control and closes with reason: "actionClick" after its consumer click handler runs. It renders a native button unless composed.

PropTypeDefault
asChildbooleanfalse
renderRenderProp-
Data attributeValues
[data-slot]"alert-dialog-action"

Examples

Destructive Confirmation

tsx
import { AlertDialog } from "@flowstack-ui/atom";

export function DeleteProjectDialog() {
  return (
    <AlertDialog.Root>
      <AlertDialog.Trigger>Delete project</AlertDialog.Trigger>
      <AlertDialog.Portal>
        <AlertDialog.Overlay />
        <AlertDialog.Content>
          <AlertDialog.Title>Delete project?</AlertDialog.Title>
          <AlertDialog.Description>
            This permanently removes the project and cannot be undone.
          </AlertDialog.Description>
          <AlertDialog.Cancel>Keep project</AlertDialog.Cancel>
          <AlertDialog.Action>Delete project</AlertDialog.Action>
        </AlertDialog.Content>
      </AlertDialog.Portal>
    </AlertDialog.Root>
  );
}

Track the Decision

tsx
import { AlertDialog } from "@flowstack-ui/atom";

export function TrackedAlertDialog() {
  return (
    <AlertDialog.Root
      onOpenChange={(open, reason) => {
        if (!open) {
          console.log(`Alert dialog closed: ${reason ?? "unknown"}`);
        }
      }}
    >
      <AlertDialog.Trigger>Reset settings</AlertDialog.Trigger>
      <AlertDialog.Content>
        <AlertDialog.Title>Reset every setting?</AlertDialog.Title>
        <AlertDialog.Description>
          Your preferences will return to their original values.
        </AlertDialog.Description>
        <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
        <AlertDialog.Action>Reset settings</AlertDialog.Action>
      </AlertDialog.Content>
    </AlertDialog.Root>
  );
}

Accessibility

AlertDialog follows the WAI-ARIA Alert Dialog pattern. Content has role="alertdialog" and aria-modal="true". Provide a Title or an ariaLabel, and always provide a Description that clearly explains the consequence. Cancel receives initial focus by default so destructive dialogs do not place focus on the destructive action.

Focus stays inside the open dialog and returns to the Trigger after closing. Backdrop clicks do not dismiss the dialog.

KeyDescription
EscapeCloses the alert dialog when closeOnEscape is enabled.
TabMoves to the next focusable element, wrapping inside Content.
Shift+TabMoves to the previous focusable element, wrapping inside Content.

Changelog

Unreleased

0.24.0

  • Added public Agent Knowledge for component selection, required composition, recurring mistakes, and validation.

0.20.3

  • Inherited document-only overflow locking so sticky application chrome remains anchored while AlertDialog is open at a nonzero page scroll position.

0.6.7

  • Inherited root/body overflow locking without fixed-body repositioning or unlock-time scroll restoration, avoiding iOS Safari browser-toolbar transitions.

0.3.4

  • Inherited corrected scroll-lock compensation so opening or closing AlertDialog no longer shifts pages that preserve their scrollbar gutter.

0.3.2

  • Inherited corrected nested-modal isolation handoff and final inert restoration from the shared Modal foundation.

0.3.1

  • Inherited reliable exit-presence cleanup so closed AlertDialog Content and Overlay cannot remain above the page when CSS emits no end event.

0.3.0

  • Fixed native Content ARIA precedence and registered Title/Description relationships across SSR and hydration.
  • Added a settled development warning when an alert dialog lacks its required accessible description.
  • Added Content-level initialFocus and finalFocus while preserving Cancel's native safe autofocus default.
  • Added shared top-layer ownership and Modal.Branch support for third-party alert-dialog portals.
  • Preserved descendant composite and nested-modal keyboard contracts through metadata-aware focus containment.
  • Added registered-portal scroll allowances and per-document wheel/touch background containment with exact cleanup.
  • Added stack-aware background isolation and same-document HTMLElement portal-container enforcement through the shared Modal foundation.
  • Established shared modal ownership before paint and made exit-present Content inert and accessibility-hidden with uninterrupted nested lock handoff.
  • Rejected Content nested beneath the accessibility-hidden Overlay.

0.2.0

  • Added shared dismissable layer Escape handling so nested overlays close before the parent alert dialog closes.
  • Improved modal focus containment, including support for registered portalled layers owned by descendants inside the alert dialog.

0.1.0

  • Initial Atom release.