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.
'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”).
| Prop | Type | Description |
|---|---|---|
butted | | Butts the button against an adjacent field on its |
handle | | Associates a detached trigger with an AlertDialog created by |
icon | | One optional functional glyph (§6.10), inline tier, FILL 0. |
iconOnly | |
|
iconPosition | | Which side of the label the glyph sits on (a |
onMedia | | Only with |
onPress | | Called on click, after |
payload | | A payload handed to the AlertDialog’s children function when this trigger opens it. |
primary | | Primary Radix scale, from the primary roster: the outline edge, labels and focus ring. Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: the |
size | | Fixed height: |
variant | |
|
children | | 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.
| Prop | Type | Description |
|---|---|---|
cover | | The optional solid cover, from --md-n-above: the page ground’s
companion field [D177] (paper, white, meadow and pollen → |
primary | | Primary Radix scale inside the panel’s |
secondary | | Secondary Radix scale inside the panel: accents only. Omitted, the white preset’s default. |
status | | Required (no default): |
statusLabel | | The glyph’s accessible name; defaults to the English status word. |
container | | The element the portal renders into. Default: |
keepMounted | | 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.
| Prop | Type | Description |
|---|---|---|
butted | | Butts the button against an adjacent field on its |
icon | | One optional functional glyph (§6.10), inline tier, FILL 0. |
iconOnly | |
|
iconPosition | | Which side of the label the glyph sits on (a |
onMedia | | Only with |
onPress | | Called on click, after |
primary | | Primary Radix scale, from the primary roster: the outline edge, labels and focus ring. Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: the |
size | | Fixed height: |
variant | |
|
children | | The label: verb plus object, authored in title case [D160]. |
Additional types
alertDialog
type alertDialog = alertDialogAlertDialogActionsProps
Props for AlertDialogActions: DialogActions props.
type AlertDialogActionsProps = DialogActionsPropsAlertDialogBodyProps
Props for AlertDialogBody: DialogBody props.
type AlertDialogBodyProps = DialogBodyPropsAlertDialogCancelProps
Props for AlertDialogCancel: Button props for the outline twin that dismisses the alert.
type AlertDialogCancelProps = ButtonPropsAlertDialogDescriptionProps
Props for AlertDialogDescription: DialogDescription props.
type AlertDialogDescriptionProps = DialogDescriptionPropsAlertDialogPopupProps
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 = DialogTitlePropsAlertDialogTriggerProps
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).