FairGarden Design

Alert Dialog

Confirms a destructive or irreversible action: a status glyph, a question, one consequence sentence and two named actions.

import {
  AlertDialog,
  AlertDialogActions,
  AlertDialogBody,
  AlertDialogCancel,
  AlertDialogDescription,
  AlertDialogPopup,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@fairgarden-private/design/components/AlertDialog'

The alert uses the Dialog's panel: a portaled white scope with the --ds-stroke-3 frame, a full-screen sheet below 768 px and centered from it, with no tinted backdrop. status is required: danger draws ◆ with its ×, warning ▲ with its !, in the status scale above the title. There is no close X: AlertDialogCancel, the least destructive action, takes focus on open, and Esc means Cancel. An outside press never dismisses it.

Both buttons name the act ("Delete 3 Photos" and "Keep Photos"), never "OK". The glyph and the words carry the seriousness, not a red fill. While the confirmation runs, the solid Button reads "Deleting…" at its rest width and the alert stays open.

Danger and warning

Danger and warning

Nothing deleted or discarded yet.

AlertDialogStatuses.tsx
'use client'

import * as React from 'react'
import { Button } from '@fairgarden-private/design/components/Button'
import {
  AlertDialog,
  AlertDialogActions,
  AlertDialogBody,
  AlertDialogCancel,
  AlertDialogDescription,
  AlertDialogPopup,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@fairgarden-private/design/components/AlertDialog'
import styles from './statuses.module.css'

export function AlertDialogStatuses() {
  const [deleteOpen, setDeleteOpen] = React.useState(false)
  const [discardOpen, setDiscardOpen] = React.useState(false)
  const [deleting, setDeleting] = React.useState(false)
  const [log, setLog] = React.useState('Nothing deleted or discarded yet.')

  // Busy: the confirming button reads "Deleting…" and the alert stays open until the result.
  const deletePhotos = () => {
    setDeleting(true)
    window.setTimeout(() => {
      setDeleting(false)
      setDeleteOpen(false)
      setLog('Deleted 3 photos from the trail report.')
    }, 1200)
  }

  return (
    <div className={styles.stack}>
      <div className={styles.row}>
        <AlertDialog
          open={deleteOpen}
          onOpenChange={(open) => {
            if (!deleting) setDeleteOpen(open)
          }}
        >
          <AlertDialogTrigger variant="outline">Delete Photos</AlertDialogTrigger>
          <AlertDialogPopup status="danger">
            <AlertDialogBody>
              <AlertDialogTitle>Delete 3 photos?</AlertDialogTitle>
              <AlertDialogDescription>
                They leave the trail report for everyone and can’t be restored.
              </AlertDialogDescription>
            </AlertDialogBody>
            <AlertDialogActions>
              <Button variant="solid" aria-busy={deleting} onClick={deletePhotos}>
                {deleting ? 'Deleting…' : 'Delete 3 Photos'}
              </Button>
              <AlertDialogCancel disabled={deleting}>Keep Photos</AlertDialogCancel>
            </AlertDialogActions>
          </AlertDialogPopup>
        </AlertDialog>

        <AlertDialog open={discardOpen} onOpenChange={setDiscardOpen}>
          <AlertDialogTrigger variant="outline">Discard Changes</AlertDialogTrigger>
          <AlertDialogPopup status="warning">
            <AlertDialogBody>
              <AlertDialogTitle>Discard your edits to Ridge Loop?</AlertDialogTitle>
              <AlertDialogDescription>
                Changes made since you opened the trail are lost.
              </AlertDialogDescription>
            </AlertDialogBody>
            <AlertDialogActions>
              <Button
                variant="solid"
                onClick={() => {
                  setDiscardOpen(false)
                  setLog('Discarded the edits to Ridge Loop.')
                }}
              >
                Discard Edits
              </Button>
              <AlertDialogCancel>Keep Editing</AlertDialogCancel>
            </AlertDialogActions>
          </AlertDialogPopup>
        </AlertDialog>
      </div>
      <p className={styles.status} aria-live="polite">
        {log}
      </p>
    </div>
  )
}

API Reference

AlertDialog and AlertDialogTrigger

Groups the parts of an alert dialog (Base UI AlertDialog.Root): always modal, and an outside press never dismisses it. It may open over a Dialog, never deeper; the Dialog stays visible, framed and inert.

Opens the alert dialog. Renders a Button (§9.2) whose label names the act (“Delete Photos”).

PropTypeDescription
butted
'start' | 'end' | null | undefined

Butts the button against an adjacent field on its start or end edge, as in the butted submit. Default: none.

handle
AlertDialogHandle<unknown> | undefined

Associates a detached trigger with an AlertDialog created by AlertDialog.createHandle.

icon
| 'menu'
| 'search'
| 'circle'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'chevron_right'
| 'chevron_left'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail'
| undefined

One optional functional glyph (§6.10), inline tier, FILL 0.

iconOnly
boolean | null | undefined

true hides the label and shows only icon. Default false.

iconPosition
'start' | 'end' | undefined

Which side of the label the glyph sits on (a text Button’s chevron trails).

onMedia
false | boolean | null | undefined

Only with iconOnly.

onPress
PressCallback | undefined

Called on click, after onClick.

payload
unknown | undefined

A payload handed to the AlertDialog’s children function when this trigger opens it.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale, from the primary roster: the outline edge, labels and focus ring. Never defaulted; omitted, it inherits the scope [D133].

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale: the solid fill and the text glyph. Omitted, solid falls back to the scope’s action scale.

size
'sm' | 'md' | 'lg' | 'xl' | null | undefined

Fixed height: sm 32 px (hit area extended to 44), md 40 px (default), lg 48 px, xl 56 px for the page’s single transactional action.

variant
'text' | 'solid' | 'outline' | null | undefined

outline (default) is the outline twin; solid is the page’s primary action, filled with the scope’s action scale; text is a text button whose glyph trails (§9.2).

children
React.ReactNode | undefined

The label: verb plus object, authored in title case [D160].

AlertDialogPopup

The alert panel: the Dialog panel (--size-sm, white scope, --ds-stroke-3 --primary12 frame, full-screen sheet below --md-n-above) with the status glyph above the title. Compose AlertDialogBody (title and one consequence sentence) and AlertDialogActions inside it; there is no close X, only AlertDialogCancel, which takes focus on open.

PropTypeDescription
cover
| 'pink'
| 'amber'
| 'forest'
| 'leaf'
| 'clay'
| 'royal'
| 'brick'
| undefined

The optional solid cover, from --md-n-above: the page ground’s companion field [D177] (paper, white, meadow and pollen → forest; tide and heather → royal; apricot and rose → brick; see companionField from Ground). It renders as a kind="field" Ground of that preset at full-viewport geometry with no edge, fully opaque, hiding the page [D88 → D121]. Omitted (default), no backdrop is painted.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale inside the panel’s white scope: text, frame and rules. Omitted, the white preset’s default; never the trigger’s [D133].

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale inside the panel: accents only. Omitted, the white preset’s default.

status
AlertDialogStatus | undefined

Required (no default): warning draws ▲ with !, danger ◆ with ×, at the block tier above the title [D58]. The glyph and the words carry the seriousness, never a red fill.

statusLabel
string | undefined

The glyph’s accessible name; defaults to the English status word.

container
| HTMLElement
| ShadowRoot
| React.RefObject<HTMLElement | ShadowRoot | null>
| null
| undefined

The element the portal renders into. Default: document.body.

keepMounted
boolean | undefined

Keeps the portal mounted while closed.

AlertDialogBody, AlertDialogTitle and AlertDialogDescription

Holds the title and description under the glyph; scrolls with overflow edges like the Dialog body.

The alert’s title and accessible name, in type-itemhead: the question (“Delete 3 photos?").

One consequence sentence, in type-body-ui --primary12.

AlertDialogActions and AlertDialogCancel

The action bar: the confirming solid Button first (“Delete 3 Photos”, “Deleting…” while busy), then AlertDialogCancel. Stacked below --md-n-above, one row from it.

The least destructive action, and the alert’s only way out besides Esc: an outline Button that closes the alert and takes focus when it opens. Name the act it keeps (“Keep Photos”), never “OK” or “Cancel” alone.

PropTypeDescription
butted
'start' | 'end' | null | undefined

Butts the button against an adjacent field on its start or end edge, as in the butted submit. Default: none.

icon
| 'menu'
| 'search'
| 'circle'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'chevron_right'
| 'chevron_left'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail'
| undefined

One optional functional glyph (§6.10), inline tier, FILL 0.

iconOnly
boolean | null | undefined

true hides the label and shows only icon. Default false.

iconPosition
'start' | 'end' | undefined

Which side of the label the glyph sits on (a text Button’s chevron trails).

onMedia
false | boolean | null | undefined

Only with iconOnly.

onPress
PressCallback | undefined

Called on click, after onClick.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale, from the primary roster: the outline edge, labels and focus ring. Never defaulted; omitted, it inherits the scope [D133].

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale: the solid fill and the text glyph. Omitted, solid falls back to the scope’s action scale.

size
'sm' | 'md' | 'lg' | 'xl' | null | undefined

Fixed height: sm 32 px (hit area extended to 44), md 40 px (default), lg 48 px, xl 56 px for the page’s single transactional action.

variant
'text' | 'solid' | 'outline' | null | undefined

outline (default) is the outline twin; solid is the page’s primary action, filled with the scope’s action scale; text is a text button whose glyph trails (§9.2).

children
React.ReactNode | undefined

The label: verb plus object, authored in title case [D160].

Additional types

alertDialog
AlertDialogActionsProps

Props for AlertDialogActions: DialogActions props.

type AlertDialogActionsProps = DialogActionsProps
AlertDialogBodyProps

Props for AlertDialogBody: DialogBody props.

type AlertDialogBodyProps = DialogBodyProps
AlertDialogCancelProps

Props for AlertDialogCancel: Button props for the outline twin that dismisses the alert.

type AlertDialogCancelProps = ButtonProps
AlertDialogDescriptionProps

Props for AlertDialogDescription: DialogDescription props.

type AlertDialogDescriptionProps = DialogDescriptionProps
AlertDialogPopupProps

Props for AlertDialogPopup: DialogPopup props (without wide) plus the required status.

type AlertDialogPopupProps = {
  /**
   * Primary Radix scale inside the panel's `white` scope: text, frame and
   * rules. Omitted, the white preset's default; never the trigger's [D133].
   */
  primary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  /** Secondary Radix scale inside the panel: accents only. Omitted, the white preset's default. */
  secondary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
  /**
   * The optional solid cover, from --md-n-above: the page ground's
   * companion field [D177] (paper, white, meadow and pollen → `forest`;
   * tide and heather → `royal`; apricot and rose → `brick`; see
   * `companionField` from Ground). It renders as a `kind="field"` Ground of
   * that preset at full-viewport geometry with no edge, fully opaque,
   * hiding the page [D88 → D121]. Omitted (default), no backdrop is painted.
   */
  cover?: 'pink' | 'amber' | 'forest' | 'leaf' | 'clay' | 'royal' | 'brick';
  /** The element the portal renders into. Default: `document.body`. */
  container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null;
  /** Keeps the portal mounted while closed. */
  keepMounted?: boolean;
  /**
   * Required (no default): `warning` draws ▲ with !, `danger` ◆ with ×, at
   * the block tier above the title [D58]. The glyph and the words carry the
   * seriousness, never a red fill.
   */
  status: AlertDialogStatus;
  /** The glyph's accessible name; defaults to the English status word. */
  statusLabel?: string;
}
AlertDialogProps

Props for AlertDialog: Base UI AlertDialog.Root props (open, onOpenChange …).

type AlertDialogProps<Payload = unknown> = AlertDialogRoot.Props<Payload>
AlertDialogStatus
type AlertDialogStatus = 'warning' | 'danger'
AlertDialogTitleProps

Props for AlertDialogTitle: DialogTitle props.

type AlertDialogTitleProps = DialogTitleProps
AlertDialogTriggerProps

Props for AlertDialogTrigger: Button props plus Base UI’s trigger handle and payload.

type AlertDialogTriggerProps = (
  | {
      iconOnly?: false | null;
      onMedia?: false | null;
      size?: 'sm' | 'md' | 'lg' | 'xl' | null;
      icon?:
        | 'menu'
        | 'search'
        | 'circle'
        | 'arrow_forward'
        | 'arrow_upward'
        | 'expand_more'
        | 'close'
        | 'remove'
        | 'add'
        | 'check'
        | 'chevron_right'
        | 'chevron_left'
        | 'more_horiz'
        | 'play_arrow'
        | 'pause'
        | 'download'
        | 'zoom_in'
        | 'zoom_out'
        | 'recenter'
        | 'help'
        | 'mail';
      children?: React.ReactNode;
    }
  | {
      iconOnly: true;
      onMedia?: boolean | null;
      size?: 'sm' | 'md' | 'lg' | null;
      icon:
        | 'menu'
        | 'search'
        | 'circle'
        | 'arrow_forward'
        | 'arrow_upward'
        | 'expand_more'
        | 'close'
        | 'remove'
        | 'add'
        | 'check'
        | 'chevron_right'
        | 'chevron_left'
        | 'more_horiz'
        | 'play_arrow'
        | 'pause'
        | 'download'
        | 'zoom_in'
        | 'zoom_out'
        | 'recenter'
        | 'help'
        | 'mail';
      children: React.ReactNode;
    }
) & {
  /**
   * `outline` (default) is the outline twin; `solid` is the page's primary
   * action, filled with the scope's action scale; `text` is a text button
   * whose glyph trails (§9.2).
   */
  variant?: 'text' | 'solid' | 'outline' | null;
  /**
   * Butts the button against an adjacent field on its `start` or `end`
   * edge, as in the butted submit. Default: none.
   */
  butted?: 'start' | 'end' | null;
  /**
   * Primary Radix scale, from the primary roster: the outline edge, labels
   * and focus ring. Never defaulted; omitted, it inherits the scope [D133].
   */
  primary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  /**
   * Secondary Radix scale: the `solid` fill and the `text` glyph. Omitted,
   * `solid` falls back to the scope's action scale.
   */
  secondary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
  /** Which side of the label the glyph sits on (a `text` Button's chevron trails). */
  iconPosition?: 'start' | 'end';
  /**
   * Called on click, after `onClick`.
   * @deprecated Use `onClick`. Kept as an alias from the react-aria Button.
   */
  onPress?: PressCallback;
  /** Associates a detached trigger with an AlertDialog created by `AlertDialog.createHandle`. */
  handle?: AlertDialogHandle<unknown>;
  /** A payload handed to the AlertDialog's children function when this trigger opens it. */
  payload?: unknown;
}

Specification: DESIGN-SYSTEM.md §10.14 (alert dialog), §1.5.4 (status glyphs) and §1.9.2 (destructive confirmations).