Components / overlays

AlertDialog

This component was flagged as possibly broken in production before the first release: it had no CSS of its own and leaned on rules that only apply to a native <dialog>, which it is not. It rendered unpositioned and undimmed with a live focus trap. Fixed, and the specimen now proves it renders.

Rendered from the package · open on its own

Summary

A modal that interrupts to confirm something consequential — deleting, publishing, discarding.

APG patternAlert Dialog
Built onRadix
Statusstable
Since0.1.0
<AlertDialog open={open} onOpenChange={setOpen}
  title="Delete Northlight?"
  description="The case study and its 12 images are removed permanently."
  confirmLabel="Delete permanently" onConfirm={destroy} />

Anatomy

A portalled panel carrying role="alertdialog", over its own backdrop.

PartElementRequired
bodythe caller'sthe caller's
closethe caller'sthe caller's
description<radixalertdialog.description>yes
footer<div>yes
headerthe caller'sthe caller's
label<span>yes
title<radixalertdialog.title>yes

Properties

PropertyValuesDefaultWhat it does
openbooleannoneWhether the dialog is showing.
onOpenChange(open: boolean) => voidnoneCalled when the reader dismisses it with Escape or Cancel.
titleReact.ReactNoderequiredThe question, naming the thing — "Delete Northlight?" rather than "Are you sure?". It is the accessible name.
descriptionReact.ReactNoderequiredWhat is lost and whether it comes back. Required, because role="alertdialog" announces it on open and an alert dialog with nothing to announce should have been a Dialog.
confirmLabelReact.ReactNoderequiredRepeat the verb: "Delete permanently", never "OK".
cancelLabelReact.ReactNodeCancelThe way out. Focus lands here on open, so Enter on arrival cancels rather than destroys.
onConfirm() => voidnoneCalled when the reader confirms. The dialog does not close itself — the caller decides what happens after the work succeeds or fails.
intentnegative · accent · positive · warningnegativeThe confirm button's intent. negative by default, because that is what an alert dialog is usually for.
triggerReact.ReactNodenoneThe control that opens it, wired via asChild.

States

StateExpressed as
Pressed:active
Disabled:disabled
Hover:hover
Open or closeddata-state

Behavior

Three differences from Dialog, all deliberate. role="alertdialog" so the message is announced on open rather than waiting to be read. Focus lands on Cancel rather than the destructive action. And there is no dismiss-by-outside-click — a decision this size should not be closable by a stray tap.

Escape still closes, because a reader must always have a way out that is not the destructive one.

Accessibility

role="alertdialog" announces the description on open. That is the entire reason it is not a Dialog: the reader hears what is at stake before they can act.

Focus on Cancel is the safety. Enter on arrival must not destroy anything.

Keyboard

KeyResult
EscapeCloses — a reader must always have a way out that is not the destructive one
TabCycles inside the panel

Roles, states and properties

AttributeSet by
aria-modalalways true
rolealways alertdialog

Focus

Focus lands on Cancel, not on the confirm button. Enter on arrival must not destroy anything. There is no dismiss-by-outside-click; focus returns to the trigger on close.

Screen reader

Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.

Contrast and target size

npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.

It takes the touch floor under (pointer: coarse) — the visual size is unchanged on a desktop pointer.

Tested

  • test/apg/batch-a.test.jsx
  • test/apg/partials-sweep.test.jsx
  • test/browser/keyboard.spec.mjs
  • test/browser/upstream-natives.spec.mjs

Known gaps

  • 2 upstream refs — APG's own page or markup, not the pattern

Appearance

TokenUsed for
--se-accentintent
--se-bgbackground, border-color
--se-borderborder, border-color
--se-border-widthborder, border-width
--se-control-hblock-size, inline-size
--se-control-h-lgblock-size, inline-size
--se-control-h-smblock-size, inline-size
--se-duration-instanttransition
--se-duration-mediumanimation
--se-ease-decelerateanimation
--se-ease-standardtransition
--se-layer-backdropz-index
--se-layer-overlayz-index
--se-leading-bodyline-height
--se-leading-headingline-height
--se-mix-downbackground, border-color
--se-mix-hoverbackground, border-color
--se-mix-lineborder-color
--se-mix-pressbackground
--se-mix-shadebackground, border-color
--se-mix-softbackground
--se-negativeintent
--se-positiveintent
--se-radius-lgborder-radius
--se-radius-mdborder-radius
--se-shadow-overlaybox-shadow
--se-space-2gap
--se-space-3gap, padding, padding-inline
--se-space-4gap, padding-inline
--se-space-5padding, padding-block-end, padding-block-start, padding-inline
--se-surfacebackground
--se-surface-sunkbackground
--se-target-minblock-size, inline-size, min-block-size
--se-textbackground, color, intent
--se-text-disabledcolor
--se-text-mdfont-size
--se-text-mutedcolor
--se-text-on-intentcolor
--se-text-smfont-size
--se-text-xsfont-size
--se-tracking-headingletter-spacing
--se-weight-semiboldfont-weight

The portalled variant carries the same surface as the native dialog by hand, because there is no shared floating surface to inherit — one of the four that have already drifted, recorded in 3.5.

Motion

Entry over --se-duration-medium, gated so axe never measures it mid-fade.

Content

The confirm label repeats the verb. Delete permanently tells a reader who arrived mid-sentence what they are agreeing to; OK and Confirm do not.

The description says what is lost and whether it can come back.

Usage

Use Dialog for anything the reader can undo, and nothing at all for something reversible with a toast.

One question per alert dialog. If there are two decisions, the first one has not been made yet.

Notes

Radix is permitted here — Alert Dialog is one of the six patterns on the closed list, and it supplies the focus scope and the portal.

This component was flagged as possibly broken in production before the first release: it had no CSS of its own and leaned on rules that only apply to a native <dialog>, which it is not. It rendered unpositioned and undimmed with a live focus trap. Fixed, and the specimen now proves it renders.