Skip to content
UI

Alert Dialog

A dialog that interrupts the user to confirm an irreversible or consequential action.

@zenoxai/ui/components/alert-dialogBase UI Markdown
function AlertDialogDemo() {
  const [open, setOpen] = React.useState(false)

  return (
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger render={<Button variant="destructive" />}>
        Delete rule
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Delete detection rule?</AlertDialogTitle>
          <AlertDialogDescription>
            &ldquo;Impossible travel&rdquo; stops raising alerts in every
            tenant. Alerts it already raised stay in the queue.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction
            variant="destructive"
            onClick={() => setOpen(false)}
          >
            Delete rule
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Usage

import * as React from "react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@zenoxai/ui/components/alert-dialog"
import { Button } from "@zenoxai/ui/components/button"

function DeleteRuleButton() {
  const [open, setOpen] = React.useState(false)

  return (
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger render={<Button variant="destructive" />}>
        Delete rule
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Delete detection rule?</AlertDialogTitle>
          <AlertDialogDescription>
            It stops raising alerts in every tenant.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction
            variant="destructive"
            onClick={async () => {
              await deleteRule()
              setOpen(false)
            }}
          >
            Delete rule
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Use an alert dialog before an action the user cannot undo or would not expect: deleting a rule, revoking a key, archiving alerts in bulk. It asks one question and offers two answers. Unlike Dialog, a click outside does not close it: the user has to choose.

  • For a task with fields (rename, create, edit), use Dialog.
  • For an action that is easy to undo, skip the confirmation: run it and offer “Undo” in a toast.
  • To ask for confirmation from an event handler with one call (await confirm({...})), use the Confirm Dialog pattern, which is built on this component.

AlertDialogCancel closes the dialog. AlertDialogAction is a plain Button: it runs your onClick and does not close the dialog, so control open and close it when the action is done. Write the title as the question (“Delete detection rule?”) and repeat the verb on the action button (“Delete rule”), never “OK” or “Yes”.

Examples

With an icon

AlertDialogMedia holds an icon above the title, or beside it from the sm breakpoint up.

function AlertDialogWithMedia() {
  const [open, setOpen] = React.useState(false)

  return (
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Revoke API key
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogMedia>
            <KeyRoundIcon />
          </AlertDialogMedia>
          <AlertDialogTitle>Revoke API key?</AlertDialogTitle>
          <AlertDialogDescription>
            Integrations that use &ldquo;siem-export&rdquo; stop sending events
            immediately. You cannot restore a revoked key.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Keep key</AlertDialogCancel>
          <AlertDialogAction
            variant="destructive"
            onClick={() => setOpen(false)}
          >
            Revoke key
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Small

size="sm" narrows the dialog, centers the text and splits the footer into two equal buttons. Use it for one-line questions.

function AlertDialogSmall() {
  const [open, setOpen] = React.useState(false)

  return (
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Sign out everywhere
      </AlertDialogTrigger>
      <AlertDialogContent size="sm">
        <AlertDialogHeader>
          <AlertDialogTitle>Sign out of all sessions?</AlertDialogTitle>
          <AlertDialogDescription>
            You will need to sign in again on every device.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction onClick={() => setOpen(false)}>
            Sign out
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Pending action

When the action calls the server, keep the dialog open until the request finishes. Disable both buttons, show a Spinner on the action, and ignore close requests in onOpenChange while it runs.

function AlertDialogPending() {
  const [open, setOpen] = React.useState(false)
  const [pending, setPending] = React.useState(false)

  function archive() {
    setPending(true)
    // Stands in for the request that archives the alerts.
    setTimeout(() => {
      setPending(false)
      setOpen(false)
    }, 1200)
  }

  return (
    <AlertDialog
      open={open}
      onOpenChange={(next) => {
        if (!pending) setOpen(next)
      }}
    >
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Archive 12 alerts
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Archive 12 alerts?</AlertDialogTitle>
          <AlertDialogDescription>
            Archived alerts leave the queue and stop counting toward the SLA.
            You can restore them from the archive for 90 days.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
          <AlertDialogAction onClick={archive} disabled={pending}>
            {pending && <Spinner data-icon="inline-start" />}
            {pending ? "Archiving" : "Archive alerts"}
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Accessibility

  • The popup has role="alertdialog". Focus moves into it on open, stays inside and returns to the trigger on close. Escape closes it; a click on the backdrop does not.
  • AlertDialogTitle names the dialog and AlertDialogDescription describes it. Render both: the description is where the consequence is spelled out.

API reference

Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.

AlertDialog

AlertDialog is a modal that stops the user to confirm a consequential action, such as deleting a record or discarding changes. Unlike Dialog, it does not close on an outside click. For a plain yes or no, call `confirm()` from the ConfirmDialog pattern instead of composing one; for a form or other content, use Dialog.

PropTypeDefault
actionsRefRefObject<DialogRootActions | null>

A ref to imperative actions. - `unmount`: Ends the closing phase of the alert dialog after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the alert dialog completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the alert dialog imperatively when called.

-
defaultOpenboolean

Whether the dialog is initially open.

false
defaultTriggerIdstring | null

ID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog.

-
handleAlertDialogHandle<unknown>

A handle to associate the alert dialog with a trigger. If specified, allows external triggers to control the alert dialog's open state. Can be created with the AlertDialog.createHandle() method.

-
onOpenChange((open: boolean, eventDetails: AlertDialogRootChangeEventDetails) => void)

Event handler called when the alert dialog is opened or closed.

-
onOpenChangeComplete((open: boolean) => void)

Event handler called after any animations complete when the dialog is opened or closed.

-
openboolean

Whether the dialog is currently open.

-
triggerIdstring | null

ID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the `open` prop is not set).

-

AlertDialogAction

AlertDialogAction is the confirming button. It does not close the dialog: run the action, then close the dialog through its controlled `open` state, so a failed request can keep it open.

PropTypeDefault
focusableWhenDisabledboolean

Whether the button should be focusable when disabled.

false
nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).

true
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ButtonState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null

Height: `xs` 24px, `sm` 28px, `default` 32px, `lg` 36px. The `icon-*` sizes are square buttons of the same heights for a single icon. The icon follows the size name: 12px at `xs`, 14px at `sm`, 16px at `default` and `lg`.

-
variant"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null

Visual weight. `default` is the filled primary action, one per view; `outline` and `secondary` are for the other actions; `ghost` has no fill until hover, for toolbars and icon buttons; `destructive` is for delete and other irreversible actions; `link` looks like inline text.

-

AlertDialogCancel

AlertDialogCancel closes the dialog without acting. It renders an outline Button and takes its `variant` and `size`.

PropTypeDefault
nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).

true
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogCloseState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null

Height: `xs` 24px, `sm` 28px, `default` 32px, `lg` 36px. The `icon-*` sizes are square buttons of the same heights for a single icon. The icon follows the size name: 12px at `xs`, 14px at `sm`, 16px at `default` and `lg`.

default
variant"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null

Visual weight. `default` is the filled primary action, one per view; `outline` and `secondary` are for the other actions; `ghost` has no fill until hover, for toolbars and icon buttons; `destructive` is for delete and other irreversible actions; `link` looks like inline text.

outline

AlertDialogContent

AlertDialogContent is the dialog box, rendered in a portal over a dimmed backdrop. Like DialogContent, it is never taller than the viewport less a 16px margin on each side and scrolls when its content is longer.

PropTypeDefault
finalFocusboolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the dialog is closed.

-
initialFocusboolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard.

-
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogPopupState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
size"default" | "sm"

`default` is 400px wide from the sm breakpoint, as wide as a `sm` Dialog, with the header aligned left. `sm` stays 320px wide, centers the header and lays the footer buttons out in two equal columns.

default

AlertDialogDescription

PropTypeDefault
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogDescriptionState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-

AlertDialogFooter

No props of its own. Accepts the props of the element it renders.

AlertDialogHeader

No props of its own. Accepts the props of the element it renders.

AlertDialogMedia

AlertDialogMedia is the icon tile beside or above the title, for a symbol that sets the tone, such as a warning sign.

No props of its own. Accepts the props of the element it renders.

AlertDialogOverlay

PropTypeDefault
forceRenderboolean

Whether the backdrop is forced to render even when nested.

false
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogBackdropState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-

AlertDialogPortal

PropTypeDefault
containerHTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null

A parent element to render the portal element into.

-
keepMountedboolean

Whether to keep the portal mounted in the DOM while the popup is hidden.

false
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogPortalState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-

AlertDialogTitle

PropTypeDefault
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTitleState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-

AlertDialogTrigger

PropTypeDefault
handleAlertDialogHandle<unknown>

A handle to associate the trigger with an alert dialog. Can be created with the AlertDialog.createHandle() method.

-
idstring

ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop).

-
nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).

true
payloadunknown

A payload to pass to the dialog when it is opened.

-
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTriggerState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-