DocsPlatform

AlertDialog

AlertDialog is Dialog's confirmation-focused sibling. It keeps the same accessible modal anatomy and focus behaviour, but dismissal is off by default so an accidental Escape or outside click cannot lose a decision.

Explore the layers

Machine is framework-free state. Headless turns that state into accessible parts. Styled applies Grassroot's visual recipe. Vanilla mounts the same contract without a framework.

Install

npm i @grassroot/ui-machines

ts
import { createService } from "@grassroot/statechart"
import { alertDialogMachine } from "@grassroot/ui-machines/alert-dialog"
const service = createService(alertDialogMachine, {
input: { defaultOpen: false },
runCommand: (command) => console.log(command),
})
service.start()
service.send({ type: "OPEN", reason: "programmatic" })
// Escape and outside-click are no-ops until dismissable is enabled.

Behaviour contract

  • Open state: use open for controlled state or defaultOpen for state owned by the component.
  • Dismissal: dismissable defaults to false. Escape and outside click do nothing until it is enabled.
  • Commands: a change request reports the proposed value and reason; controlled consumers decide when to adopt it.
  • No-ops: opening an open dialog or closing a closed dialog produces no change request.
  • Forms: none.

Dismissal & focus

When open, AlertDialog enters the modal layer stack, traps focus inside its content, and locks document scrolling. The topmost layer owns Escape and outside-click handling. With the default non-dismissable setting, those events are intentionally ignored; a close trigger or programmatic action remains available.

Anatomy

AlertDialog.Trigger       data-part="trigger"       optional opener
AlertDialog.Backdrop      data-part="backdrop"      modal scrim
AlertDialog.Positioner    data-part="positioner"    fixed overlay wrapper
AlertDialog.Content       data-part="content"       role="alertdialog" aria-modal="true"
AlertDialog.Title         data-part="title"         labelled heading
AlertDialog.Description   data-part="description"   supporting copy
AlertDialog.CloseTrigger  data-part="close-trigger" dismissal action

Root owns the service and renders no element. Rendered parts expose data-scope="alert-dialog", their part name, and open/closed state. Content mounts only while open and receives label and description references when those parts are present.

Keyboard

Tab and Shift+Tab stay inside the content while it is open. Escape is handled only for a dismissable AlertDialog; with the default setting it is a no-op. Closing returns focus to the trigger when one exists.

Accessibility

Content uses role="alertdialog" and aria-modal="true". Titles and descriptions are connected only when rendered, so the panel never points at an absent id. The modal layer and focus trap are shared headless managers rather than framework-specific event handlers.

Evidence & further reading