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
Enterkeypress, 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 theinitialFocusprop if your layout requires a different order. - Required descriptions Always include
AlertDialog.TitleandAlertDialog.Description. These wire automatically toaria-labelledbyandaria-describedbyso assistive technologies immediately announce the stakes of the action.
Props
AlertDialog.Root
| Prop | Type | Default |
|---|---|---|
| open | boolean | — |
| defaultOpen | boolean | false |
| onOpenChange | (open: boolean, eventDetails) => void | — |
| handle | AlertDialogHandle<Payload> | — |
AlertDialog.Trigger
| Prop | Type | Default |
|---|---|---|
| payload | Payload | — |
| id | string | — |
| handle | AlertDialogHandle<Payload> | — |
AlertDialog.Popup
| Prop | Type | Default |
|---|---|---|
| initialFocus | boolean | RefObject | (openType) => boolean | HTMLElement | null | void | — |
| finalFocus | boolean | 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.