Alert Dialog

A confirmation dialog that interrupts until the user accepts or cancels. Backdrop clicks do not dismiss it, and only Escape or an explicit action closes it.

Default

import { Trash2Icon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { AlertDialog } from "@/components/ui/alert-dialog"

export default function Example() {
  return (
    <AlertDialog.Root>
      <AlertDialog.Trigger
        render={
          <Button variant="danger">
            <Trash2Icon />
            Discard Flight Log
          </Button>
        }
      />
      <AlertDialog.Portal>
        <AlertDialog.Backdrop />
        <AlertDialog.Popup>
          <AlertDialog.Title>Discard flight log?</AlertDialog.Title>
          <AlertDialog.Description>
            This mission data can't be recovered once you leave orbit.
          </AlertDialog.Description>
          <div className="mt-6 flex justify-end gap-2">
            <AlertDialog.Close render={<Button variant="secondary">Cancel</Button>} />
            <AlertDialog.Close render={<Button variant="danger">Discard</Button>} />
          </div>
        </AlertDialog.Popup>
      </AlertDialog.Portal>
    </AlertDialog.Root>
  )
}

Anatomy

import { AlertDialog } from "@/components/ui/alert-dialog"

<AlertDialog.Root>
  <AlertDialog.Trigger />
  <AlertDialog.Portal>
    <AlertDialog.Backdrop />
    <AlertDialog.Popup>
      <AlertDialog.Title />
      <AlertDialog.Description />
      <AlertDialog.Close />
    </AlertDialog.Popup>
  </AlertDialog.Portal>
</AlertDialog.Root>

With icon

Compose a decorative icon beside the title when the action needs a stronger visual cue. Keep severity in the title text and hide the icon from assistive tech.

import { CircleAlertIcon, DropletsIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { AlertDialog } from "@/components/ui/alert-dialog"

export default function Example() {
  return (
    <AlertDialog.Root>
      <AlertDialog.Trigger
        render={
          <Button variant="danger">
            <DropletsIcon />
            Purge Fuel Tank
          </Button>
        }
      />
      <AlertDialog.Portal>
        <AlertDialog.Backdrop />
        <AlertDialog.Popup>
          <div className="flex items-start gap-3">
            <CircleAlertIcon
              aria-hidden="true"
              className="mt-0.5 size-5 shrink-0 text-danger"
            />
            <div>
              <AlertDialog.Title>Purge the fuel tank?</AlertDialog.Title>
              <AlertDialog.Description>
                Every drop vents to atmosphere. Refueling takes another two
                orbits.
              </AlertDialog.Description>
            </div>
          </div>
          <div className="mt-6 flex justify-end gap-2">
            <AlertDialog.Close render={<Button variant="secondary">Cancel</Button>} />
            <AlertDialog.Close render={<Button variant="danger">Purge</Button>} />
          </div>
        </AlertDialog.Popup>
      </AlertDialog.Portal>
    </AlertDialog.Root>
  )
}

With tertiary action

Place an optional outline action on the left, and keep Cancel and the primary confirm grouped on the right.

import { FilePenLineIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { AlertDialog } from "@/components/ui/alert-dialog"

export default function Example() {
  return (
    <AlertDialog.Root>
      <AlertDialog.Trigger
        render={
          <Button variant="danger">
            <FilePenLineIcon />
            Overwrite Telemetry
          </Button>
        }
      />
      <AlertDialog.Portal>
        <AlertDialog.Backdrop />
        <AlertDialog.Popup>
          <AlertDialog.Title>Overwrite telemetry notes?</AlertDialog.Title>
          <AlertDialog.Description>
            The previous draft is replaced if you continue. You can postpone
            and come back later.
          </AlertDialog.Description>
          <div className="mt-6 flex items-center justify-between gap-2">
            <AlertDialog.Close
              render={<Button variant="outline">Remind me later</Button>}
            />
            <div className="flex gap-2">
              <AlertDialog.Close
                render={<Button variant="secondary">Cancel</Button>}
              />
              <AlertDialog.Close
                render={<Button variant="danger">Overwrite</Button>}
              />
            </div>
          </div>
        </AlertDialog.Popup>
      </AlertDialog.Portal>
    </AlertDialog.Root>
  )
}

With blurred backdrop

Add backdrop-blur-sm and isolation-auto on the backdrop so the second undoes the default isolate and blur can take effect.

import { BanIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { AlertDialog } from "@/components/ui/alert-dialog"

export default function Example() {
  return (
    <AlertDialog.Root>
      <AlertDialog.Trigger
        render={
          <Button variant="danger">
            <BanIcon />
            Scrub Launch
          </Button>
        }
      />
      <AlertDialog.Portal>
        <AlertDialog.Backdrop className="backdrop-blur-sm isolation-auto" />
        <AlertDialog.Popup>
          <AlertDialog.Title>Scrub the launch?</AlertDialog.Title>
          <AlertDialog.Description>
            The window closes for this orbit. Ground control will need a new
            clearance before the next attempt.
          </AlertDialog.Description>
          <div className="mt-6 flex justify-end gap-2">
            <AlertDialog.Close render={<Button variant="secondary">Cancel</Button>} />
            <AlertDialog.Close render={<Button variant="danger">Scrub</Button>} />
          </div>
        </AlertDialog.Popup>
      </AlertDialog.Portal>
    </AlertDialog.Root>
  )
}

Detached trigger

A handle from AlertDialog.createHandle() connects a trigger to a dialog anywhere else in the tree so neither has to be the other's descendant.

import { OctagonXIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { AlertDialog } from "@/components/ui/alert-dialog"

// Created once outside the component. Connects the trigger to the
// dialog without either one needing to be a descendant of the other.
const handle = AlertDialog.createHandle()

export default function Example() {
  return (
    <>
      <AlertDialog.Trigger
        handle={handle}
        render={
          <Button variant="danger">
            <OctagonXIcon />
            Abort Mission
          </Button>
        }
      />
      <AlertDialog.Root handle={handle}>
        <AlertDialog.Portal>
          <AlertDialog.Backdrop />
          <AlertDialog.Popup>
            <AlertDialog.Title>Abort the mission?</AlertDialog.Title>
            <AlertDialog.Description>
              The countdown stops immediately. Ground control will need a
              full resync before the next attempt.
            </AlertDialog.Description>
            <div className="mt-6 flex justify-end gap-2">
              <AlertDialog.Close render={<Button variant="secondary">Stand Down</Button>} />
              <AlertDialog.Close render={<Button variant="danger">Abort</Button>} />
            </div>
          </AlertDialog.Popup>
        </AlertDialog.Portal>
      </AlertDialog.Root>
    </>
  )
}

Multiple triggers with payload

Several triggers can share one handle and dialog, each passing its own payload. A function child reads it back so the dialog can render the matching copy.

import {
  DropletsIcon,
  LogOutIcon,
  RocketIcon,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { AlertDialog } from "@/components/ui/alert-dialog"

type ActionPayload = {
  title: string
  description: string
  confirmLabel: string
}

// One handle and one dialog. Each trigger passes its own payload so the
// popup can render the right copy for whichever action was pressed.
const handle = AlertDialog.createHandle<ActionPayload>()

const actions = [
  {
    label: "Abort Launch",
    icon: RocketIcon,
    payload: {
      title: "Abort the launch?",
      description:
        "Fuel drains automatically and the pad resets. This can't be undone mid-sequence.",
      confirmLabel: "Abort",
    },
  },
  {
    label: "Purge Fuel Tank",
    icon: DropletsIcon,
    payload: {
      title: "Purge the fuel tank?",
      description: "Every drop vents to atmosphere. Refueling takes another two orbits.",
      confirmLabel: "Purge",
    },
  },
  {
    label: "Sign Out Of Mission Control",
    icon: LogOutIcon,
    payload: {
      title: "Sign out of mission control?",
      description: "Unsaved telemetry notes will be lost the moment you log off.",
      confirmLabel: "Sign Out",
    },
  },
]

export default function Example() {
  return (
    <>
      {actions.map((action) => {
        const Icon = action.icon
        return (
          <AlertDialog.Trigger
            key={action.label}
            handle={handle}
            payload={action.payload}
            render={
              <Button variant="outline">
                <Icon />
                {action.label}
              </Button>
            }
          />
        )
      })}
      <AlertDialog.Root<ActionPayload> handle={handle}>
        {({ payload }) => (
          <AlertDialog.Portal>
            <AlertDialog.Backdrop />
            <AlertDialog.Popup>
              <AlertDialog.Title>{payload?.title}</AlertDialog.Title>
              <AlertDialog.Description>{payload?.description}</AlertDialog.Description>
              <div className="mt-6 flex justify-end gap-2">
                <AlertDialog.Close render={<Button variant="secondary">Cancel</Button>} />
                <AlertDialog.Close
                  render={<Button variant="danger">{payload?.confirmLabel}</Button>}
                />
              </div>
            </AlertDialog.Popup>
          </AlertDialog.Portal>
        )}
      </AlertDialog.Root>
    </>
  )
}

Usage Guidelines

  • Alert Dialog vs. Dialog Use Alert Dialog exclusively for urgent, interrupting confirmations where a deliberate choice is required. Backdrop clicks are intentionally ignored. For general content or forms, use a standard Dialog.
  • Initial focus on safe actions To prevent accidental data loss from an errant Enter keypress, focus should land on the safest action (e.g., Cancel) when the dialog opens. Because Cancel is placed first in the DOM in these examples, focus lands there naturally. Use the initialFocus prop if your layout requires a different order.
  • Required descriptions Always include AlertDialog.Title and AlertDialog.Description. These wire automatically to aria-labelledby and aria-describedby so assistive technologies immediately announce the stakes of the action.

Props

AlertDialog.Root

PropTypeDefault
openboolean
defaultOpenbooleanfalse
onOpenChange(open: boolean, eventDetails) => void
handleAlertDialogHandle<Payload>

AlertDialog.Trigger

PropTypeDefault
payloadPayload
idstring
handleAlertDialogHandle<Payload>

AlertDialog.Popup

PropTypeDefault
initialFocusboolean | RefObject | (openType) => boolean | HTMLElement | null | void
finalFocusboolean | RefObject | (closeType) => boolean | HTMLElement | null | void

This covers the parts used above. See the Base UI Alert Dialog docs for actionsRef, event details, CSS variables and data attributes.