AlertDialog

A modal that demands a decision — usually about something destructive or irreversible — and refuses to be dismissed by accident.

Basic usage

Install the package:

pnpm add @design-schema/react @design-schema/tokens

Import the stylesheet once for the app, then the component:

import '@design-schema/tokens/calm-precise/css';
import '@design-schema/react/index.css';
import { AlertDialog } from '@design-schema/react';

Examples

import { useEffect, useState, type ReactElement } from 'react';
import { AlertDialog, type AlertDialogProps, Button } from '@design-schema/react';

function AlertDialogHarness({ open: initialOpen, onConfirm, onCancel, ...rest }: AlertDialogProps): ReactElement {
  const [open, setOpen] = useState(initialOpen);
  useEffect(() => setOpen(initialOpen), [initialOpen]);

  return (
    <>
      <Button label={rest.heading} onClick={() => setOpen(true)} />
      <AlertDialog
        {...rest}
        open={open}
        onConfirm={() => {
          onConfirm?.();
          setOpen(false);
        }}
        onCancel={(reason) => {
          onCancel?.(reason);
          setOpen(false);
        }}
      />
    </>
  );
}

<AlertDialogHarness
  open
  heading="Delete 3 files?"
  description="They will be removed from all shared folders. This cannot be undone."
  confirmLabel="Delete files"
/>

Props

7 rows
PropTypeDefaultDescriptionPlatforms
open requiredboolean—

Controlled only — there is no uncontrolled mode; the consumer owns `open` and sets it false after handling `onConfirm` or `onCancel`, as in Dialog.

web, lit, rn, swiftui
heading requiredstring—

The question or statement, as a level-2 Heading at the size Heading reads from level 2 (no explicit `size`), and the accessible name ("Delete 3 files?").

Accessibility: aria-labelledby the heading; native accessibilityLabel on the modal content.

web, lit, rn, swiftui
description requiredstring—

What will happen and whether it can be undone, in one or two sentences, rendered as Text `tone="muted"` at Text's default size (the color.foreground.muted contrast pair). Required: a decision without consequences stated is not a decision.

Accessibility: aria-describedby; announced together with the title when the dialog opens.

web, lit, rn, swiftui
tonedanger | warning | infodanger

The nature of the decision. Sets the status icon (Icon `name` equal to the tone) and the confirm button's variant (danger → danger Button; warning and info → primary). Both buttons are Button size md.

web, lit, rn, swiftui
confirmLabel requiredstring—

The confirming action, restating it ("Delete files"). Never "OK" or "Yes".

web, lit, rn, swiftui
cancelLabelstring—

The declining action. Defaults to `copy.cancelLabel`.

web, lit, rn, swiftui
confirmDisabledbooleanfalse

Blocks confirm while a precondition is unmet (a typed confirmation, a loading state). Cancel always works. Forwarded to the confirm Button's own `disabled` — the Button decides what that means per platform, and on every platform it stays focusable-but-inert (aria-disabled plus a click guard on web and Lit, accessibilityState.disabled plus a press guard on native), so Confirm is still the last Tab stop; AlertDialog neither restyles it nor sets aria-disabled itself.

web, lit, rn, swiftui

Events

2 rows
EventTypeDescriptionPlatforms
onConfirmonConfirm / confirm

The user chose the confirming action. The consumer performs it and closes.

web, lit, rn, swiftui
onCancelonCancel / cancel

The user declined, by the cancel button or Escape. Fired with reason `cancel` or `escape`. A scrim click does nothing. The `close-button` entry in `overlay.dismiss` is the shared category name for the Cancel button, reported as `cancel`; there is no separate close button.

web, lit, rn, swiftui

Accessibility

Decisions this component leaves to you

Supply an accessible name. This component has no text of its own to fall back on, so without one a screen reader announces its role and nothing else.

Required props that carry accessibility meaning: heading, description. Each is described in the props table above.

Implicit ARIA role: alertdialog.

Anatomy

scrimsurfacefocusScopeiconheadingdescriptionfootercancelButtonconfirmButton

What generation guarantees

Accessible name — Every instance carries a programmatic name. Where the component has no text of its own to use, the name is a prop the consumer supplies.

Focus trap — While it is open, Tab cycles inside it; focus cannot reach the page behind.

Focus restore — On close, focus returns to whatever opened it.

Escape dismisses — Escape closes it from anywhere inside.

Inert background — While it is open, the rest of the page is inert to the pointer and to assistive technology.

Scroll lock — While it is open, the page behind it does not scroll.

Keyboard operable — Everything the pointer can do here, the keyboard can do — see the keyboard section.

Visible focus — Keyboard focus is always drawn, from the theme’s focus tokens, and is never removed.

Contrast — Text and meaningful non-text contrast meets WCAG AA; the pairs are listed below.

Reduced motion — Under `prefers-reduced-motion: reduce` the transitions are dropped rather than shortened, and the component still reaches every state.

Target size — Interactive targets are at least 24×24 CSS pixels (WCAG 2.2 Target Size, minimum).

AlertDialog keyboard support

4 rows
KeyAction
EscapeCancels (onCancel with reason escape).
TabFrom Confirm (the last button) wraps to Cancel (the first).
Shift+TabFrom Cancel wraps to Confirm.
EnterActivates the focused button as a native button activation, which on Cancel fires onCancel with reason cancel. Initial focus is on Cancel so Enter never confirms by momentum. `closes` here means the close request fires; the dialog itself stays open until the consumer sets `open` false. (focus on a button)

Contrast pairs

3 rows
ForegroundBackgroundLevel
color.foregroundcolor.overlay.surfaceAA
color.foreground.mutedcolor.overlay.surfaceAA
color.status.{tone}.iconcolor.overlay.surfaceAA (non-text)

Under `prefers-reduced-motion: reduce` the transitions are dropped rather than shortened, and the component still reaches every state.