Alert Dialog
A confirmation dialog that interrupts until the user accepts or cancels. Backdrop clicks do not dismiss it. Only Escape or an explicit action will.
Default
import { Trash2Icon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
AlertDialog,
AlertDialogBackdrop,
AlertDialogClose,
AlertDialogDescription,
AlertDialogPopup,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
export default function Example() {
return (
<AlertDialog>
<AlertDialogTrigger
render={
<Button variant="danger">
<Trash2Icon />
Discard Flight Log
</Button>
}
/>
<AlertDialogPortal>
<AlertDialogBackdrop />
<AlertDialogPopup>
<AlertDialogTitle>Discard flight log?</AlertDialogTitle>
<AlertDialogDescription>
This mission data can't be recovered once you leave orbit.
</AlertDialogDescription>
<div className="mt-6 flex justify-end gap-2">
<AlertDialogClose render={<Button variant="outline">Cancel</Button>} />
<AlertDialogClose render={<Button variant="danger">Discard</Button>} />
</div>
</AlertDialogPopup>
</AlertDialogPortal>
</AlertDialog>
)
}Anatomy
import {
AlertDialog,
AlertDialogBackdrop,
AlertDialogClose,
AlertDialogDescription,
AlertDialogPopup,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
<AlertDialog>
<AlertDialogTrigger />
<AlertDialogPortal>
<AlertDialogBackdrop />
<AlertDialogPopup>
<AlertDialogTitle />
<AlertDialogDescription />
<AlertDialogClose />
</AlertDialogPopup>
</AlertDialogPortal>
</AlertDialog>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,
AlertDialogBackdrop,
AlertDialogClose,
AlertDialogDescription,
AlertDialogPopup,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
export default function Example() {
return (
<AlertDialog>
<AlertDialogTrigger
render={
<Button variant="danger">
<DropletsIcon />
Purge Fuel Tank
</Button>
}
/>
<AlertDialogPortal>
<AlertDialogBackdrop />
<AlertDialogPopup>
<div className="flex items-start gap-3">
<CircleAlertIcon
aria-hidden="true"
className="mt-0.5 size-5 shrink-0 text-danger"
/>
<div>
<AlertDialogTitle>Purge the fuel tank?</AlertDialogTitle>
<AlertDialogDescription>
Every drop vents to atmosphere. Refueling takes another two
orbits.
</AlertDialogDescription>
</div>
</div>
<div className="mt-6 flex justify-end gap-2">
<AlertDialogClose render={<Button variant="outline">Cancel</Button>} />
<AlertDialogClose render={<Button variant="danger">Purge</Button>} />
</div>
</AlertDialogPopup>
</AlertDialogPortal>
</AlertDialog>
)
}With tertiary action
Place an optional ghost action on the left. Keep Cancel and the primary confirm grouped on the right.
import { FilePenLineIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
AlertDialog,
AlertDialogBackdrop,
AlertDialogClose,
AlertDialogDescription,
AlertDialogPopup,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
export default function Example() {
return (
<AlertDialog>
<AlertDialogTrigger
render={
<Button variant="danger">
<FilePenLineIcon />
Overwrite Telemetry
</Button>
}
/>
<AlertDialogPortal>
<AlertDialogBackdrop />
<AlertDialogPopup>
<AlertDialogTitle>Overwrite telemetry notes?</AlertDialogTitle>
<AlertDialogDescription>
The previous draft is replaced if you continue. You can postpone
and come back later.
</AlertDialogDescription>
<div className="mt-6 flex items-center justify-between gap-2">
<AlertDialogClose
render={<Button variant="ghost">Remind me later</Button>}
/>
<div className="flex gap-2">
<AlertDialogClose
render={<Button variant="outline">Cancel</Button>}
/>
<AlertDialogClose
render={<Button variant="danger">Overwrite</Button>}
/>
</div>
</div>
</AlertDialogPopup>
</AlertDialogPortal>
</AlertDialog>
)
}With blurred backdrop
Add backdrop-blur-sm and isolation-auto on the backdrop. The second undoes the default isolate so blur can take effect.
import { BanIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
AlertDialog,
AlertDialogBackdrop,
AlertDialogClose,
AlertDialogDescription,
AlertDialogPopup,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
export default function Example() {
return (
<AlertDialog>
<AlertDialogTrigger
render={
<Button variant="danger">
<BanIcon />
Scrub Launch
</Button>
}
/>
<AlertDialogPortal>
<AlertDialogBackdrop className="backdrop-blur-sm isolation-auto" />
<AlertDialogPopup>
<AlertDialogTitle>Scrub the launch?</AlertDialogTitle>
<AlertDialogDescription>
The window closes for this orbit. Ground control will need a new
clearance before the next attempt.
</AlertDialogDescription>
<div className="mt-6 flex justify-end gap-2">
<AlertDialogClose render={<Button variant="outline">Cancel</Button>} />
<AlertDialogClose render={<Button variant="danger">Scrub</Button>} />
</div>
</AlertDialogPopup>
</AlertDialogPortal>
</AlertDialog>
)
}Detached trigger
A handle from createAlertDialogHandle() 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,
AlertDialogBackdrop,
AlertDialogClose,
AlertDialogDescription,
AlertDialogPopup,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
createAlertDialogHandle,
} 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 = createAlertDialogHandle()
export default function Example() {
return (
<>
<AlertDialogTrigger
handle={handle}
render={
<Button variant="danger">
<OctagonXIcon />
Abort Mission
</Button>
}
/>
<AlertDialog handle={handle}>
<AlertDialogPortal>
<AlertDialogBackdrop />
<AlertDialogPopup>
<AlertDialogTitle>Abort the mission?</AlertDialogTitle>
<AlertDialogDescription>
The countdown stops immediately. Ground control will need a
full resync before the next attempt.
</AlertDialogDescription>
<div className="mt-6 flex justify-end gap-2">
<AlertDialogClose render={<Button variant="outline">Stand Down</Button>} />
<AlertDialogClose render={<Button variant="danger">Abort</Button>} />
</div>
</AlertDialogPopup>
</AlertDialogPortal>
</AlertDialog>
</>
)
}Multiple triggers with payload
Several triggers can share one handle and dialog. Each passes 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,
AlertDialogBackdrop,
AlertDialogClose,
AlertDialogDescription,
AlertDialogPopup,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
createAlertDialogHandle,
} 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 = createAlertDialogHandle<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 (
<AlertDialogTrigger
key={action.label}
handle={handle}
payload={action.payload}
render={
<Button variant="outline">
<Icon />
{action.label}
</Button>
}
/>
)
})}
<AlertDialog<ActionPayload> handle={handle}>
{({ payload }) => (
<AlertDialogPortal>
<AlertDialogBackdrop />
<AlertDialogPopup>
<AlertDialogTitle>{payload?.title}</AlertDialogTitle>
<AlertDialogDescription>{payload?.description}</AlertDialogDescription>
<div className="mt-6 flex justify-end gap-2">
<AlertDialogClose render={<Button variant="outline">Cancel</Button>} />
<AlertDialogClose
render={<Button variant="danger">{payload?.confirmLabel}</Button>}
/>
</div>
</AlertDialogPopup>
</AlertDialogPortal>
)}
</AlertDialog>
</>
)
}Props
AlertDialog
| Prop | Type | Default |
|---|---|---|
| open | boolean | — |
| defaultOpen | boolean | false |
| onOpenChange | (open: boolean, eventDetails) => void | — |
| handle | AlertDialogHandle<Payload> | — |
AlertDialogTrigger
| Prop | Type | Default |
|---|---|---|
| payload | Payload | — |
| id | string | — |
| handle | AlertDialogHandle<Payload> | — |
AlertDialogPopup
| 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.