Skip to main content

Alert Dialog

A modal dialog that interrupts the user with important content and expects a response. Built on top of Radix Dialog.

When to use

Use AlertDialog for destructive or irreversible confirmations (delete, sign out, leave without saving) where the user must explicitly acknowledge. AlertDialog blocks all background interaction. The content carries role="alertdialog", and a click on the backdrop does not close it: the user closes it through AlertDialog.Cancel, AlertDialog.Close, or the Escape key. For non-destructive input modals, use Dialog.

import { AlertDialog } from "@ngrok/mantle/alert-dialog";import { Button } from "@ngrok/mantle/button"; <AlertDialog.Root intent="info">	<AlertDialog.Trigger asChild>		<Button type="button" appearance="outlined" intent="neutral">			Show Info Alert Dialog		</Button>	</AlertDialog.Trigger>	<AlertDialog.Content>		<AlertDialog.Icon />		<AlertDialog.Body>			<AlertDialog.Header>				<AlertDialog.Title>Are you absolutely sure?</AlertDialog.Title>				<AlertDialog.Description>					Proident quis nisi tempor irure sunt ut minim occaecat mollit sunt.				</AlertDialog.Description>			</AlertDialog.Header>			<AlertDialog.Footer>				<AlertDialog.Cancel type="button">Cancel</AlertDialog.Cancel>				<AlertDialog.Action type="button">Continue</AlertDialog.Action>			</AlertDialog.Footer>		</AlertDialog.Body>	</AlertDialog.Content></AlertDialog.Root>;

Layering and z-index

AlertDialog.Content renders its floating layer at Tailwind z-60, Mantle's overlay tier, above every float (z-50). Floats composed inside the content portal into the positioner (data-slot="alert-dialog-positioner") and paint above the content. When multiple overlays are open, the most recently mounted overlay renders on top.

AlertDialog, Dialog, and Sheet each render their own backdrop, so stacking modal layers compounds the dim and blur effect on the layers underneath.

UI above an open alert dialog

UI that must take clicks above an open alert dialog, such as a consent banner, carries the overlay-prompt class. A click inside it does not count as an outside click. Add pointer-events-auto too, because the open alert dialog sets pointer-events: none on <body>. mantle's Toaster does both.

<div className="overlay-prompt pointer-events-auto fixed inset-x-0 bottom-0 z-max">	<ConsentBanner /></div>

The class reaches pointer input only. The open alert dialog traps keyboard focus and hides outside content from assistive technology with aria-hidden, so a keyboard or screen reader user cannot reach the banner. Put a control that every user must reach inside the alert dialog content.

Composition

Compose the parts of an AlertDialog together to build your own:

AlertDialog.Root├── AlertDialog.Trigger└── AlertDialog.Content    ├── AlertDialog.Icon    └── AlertDialog.Body        ├── AlertDialog.Header        │   ├── AlertDialog.Title        │   └── AlertDialog.Description        └── AlertDialog.Footer            ├── AlertDialog.Cancel            └── AlertDialog.Action

With TanStack Form

Compose an AlertDialog with TanStack Form and Field when the destructive action requires explicit acknowledgment. Render AlertDialog.Body as the <form> element with asChild so the header, field, and footer keep the alert dialog layout. AlertDialog.Action does not close the dialog by default: make it the submit button (type="submit") and close the dialog from the form's onSubmit, which only runs once validation passes. Control AlertDialog.Root with open / onOpenChange and reset the form on close so the acknowledgment is required every time the dialog opens.

The acknowledgment row composes Choice inside Field.Control, so the checkbox aligns to the first line of its label and the field's name and ARIA wiring flow onto the checkbox automatically.

import { AlertDialog } from "@ngrok/mantle/alert-dialog";import { Button } from "@ngrok/mantle/button";import { Checkbox } from "@ngrok/mantle/checkbox";import { Choice } from "@ngrok/mantle/choice";import { Field, toErrorMessages } from "@ngrok/mantle/field";import { useForm } from "@tanstack/react-form";import { useState } from "react";import { z } from "zod"; const formSchema = z.object({	acknowledged: z.boolean().refine((value) => value, "Acknowledge the consequences to continue."),}); function Example() {	const [open, setOpen] = useState(false);	const form = useForm({		defaultValues: {			acknowledged: false,		},		validators: {			onSubmit: formSchema,		},		onSubmit: ({ value }) => {			// Handle the destructive action with `value`, then close the dialog			handleOpenChange(false);		},	}); 	const handleOpenChange = (nextOpen: boolean) => {		setOpen(nextOpen);		if (!nextOpen) {			form.reset();		}	}; 	return (		<AlertDialog.Root intent="danger" open={open} onOpenChange={handleOpenChange}>			<AlertDialog.Trigger asChild>				<Button type="button" appearance="outlined" intent="danger">					Release domain				</Button>			</AlertDialog.Trigger>			<AlertDialog.Content>				<AlertDialog.Icon />				<AlertDialog.Body asChild>					<form						onSubmit={(event) => {							event.preventDefault();							event.stopPropagation();							void form.handleSubmit();						}}					>						<AlertDialog.Header>							<AlertDialog.Title>Release this domain?</AlertDialog.Title>							<AlertDialog.Description>								Releasing example.ngrok.app immediately removes it from your account.							</AlertDialog.Description>						</AlertDialog.Header>						<form.Field name="acknowledged">							{(field) => (								<Field.Item name={field.name}>									<Field.Control>										<Choice.Root>											<Choice.Indicator>												<Checkbox													checked={field.state.value}													onBlur={field.handleBlur}													onChange={(event) => field.handleChange(event.target.checked)}												/>											</Choice.Indicator>											<Choice.Content>												<Choice.Label>I understand the consequences</Choice.Label>												<Choice.Description>													Endpoints still using this domain will go offline, and the domain may be													claimed by another account.												</Choice.Description>											</Choice.Content>										</Choice.Root>									</Field.Control>									<Field.Errors messages={toErrorMessages(field.state.meta.errors)} />								</Field.Item>							)}						</form.Field>						<AlertDialog.Footer>							<AlertDialog.Cancel type="button">Cancel</AlertDialog.Cancel>							<AlertDialog.Action type="submit">Release domain</AlertDialog.Action>						</AlertDialog.Footer>					</form>				</AlertDialog.Body>			</AlertDialog.Content>		</AlertDialog.Root>	);}

Polymorphism

Every part of AlertDialog that renders DOM accepts asChild, which swaps the part's default element for your single child while keeping its styling, data-slot, and behavior. AlertDialog.Root is a state provider and renders no DOM, so it takes no asChild; AlertDialog.Icon already takes its element through the svg prop and clones it.

PartReach for asChild when
AlertDialog.TriggerThe control that opens the dialog is your own component — most often a Button or IconButton.
AlertDialog.ActionThe confirming action must be a link or a form submit button rather than the default Button.
AlertDialog.CancelThe dismissing control needs to be a link, or a button wired to your own handler.
AlertDialog.CloseSome other element inside the dialog should dismiss it.
AlertDialog.TitleThe heading level must change to fit the page's outline.
AlertDialog.DescriptionThe body copy needs a different element than the default paragraph.
AlertDialog.Header, AlertDialog.Body, AlertDialog.FooterThe region should be a semantic element such as <header>, <section>, or <footer>.
AlertDialog.ContentYou need to own the dialog panel element itself — rare, since Content already carries the panel chrome.

API Reference

AlertDialog.Root

Holds the open state and the required intent that AlertDialog.Icon and AlertDialog.Action read for their color.

AlertDialog renders its floating layer at Tailwind z-60, Mantle's overlay tier, above every float (z-50). Floats composed inside the content portal into the positioner (data-slot="alert-dialog-positioner") and paint above the content. When multiple overlays are open, the most recently mounted overlay renders on top.

All props from Radix Dialog.Root, plus:

PropTypeDefaultDescription
intent"info" | "danger"The tone of the alert dialog. Sets the color and icon styling to communicate whether the confirmation is informational or destructive.

AlertDialog.Trigger

A button that opens the Alert Dialog.

Radix Dialog.Trigger props.

AlertDialog.Content

The popover Alert Dialog container. Renders on top of the overlay and is centered in the viewport.

AlertDialog.Content renders its floating layer at Tailwind z-60, Mantle's overlay tier, above every float (z-50). Floats composed inside the content portal into the positioner (data-slot="alert-dialog-positioner") and paint above the content. When multiple overlays are open, the most recently mounted overlay renders on top.

AlertDialog.Content renders inside a positioner element (data-slot="alert-dialog-positioner") that lays out the content and receives floats portaled from inside the alert dialog. The full stacking contract lives at Stacking Layers.

Radix Dialog.Content props, plus:

PropTypeDefaultDescription
preferredWidth?`max-w-${string}`"max-w-md"The preferred width of the AlertDialogContent as a Tailwind max-w- class.

onInteractOutside still fires, but AlertDialog.Content calls event.preventDefault() after your handler, so an outside pointer interaction never closes the dialog.

AlertDialog.Body

Contains the main content of the alert dialog.

All props from div, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the AlertDialogBody styling onto alternative element types or your own React components.

AlertDialog.Header

Contains the header content of the dialog, including the title and description.

All props from div, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the AlertDialogHeader styling onto alternative element types or your own React components.

AlertDialog.Footer

Contains the footer content of the dialog, including the action and cancel buttons.

All props from div, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the AlertDialogFooter styling onto alternative element types or your own React components.

AlertDialog.Title

An accessible name to be announced when the dialog is opened.

Alternatively, pass aria-label or aria-labelledby to AlertDialogContent and exclude this component.

Radix Dialog.Title props.

AlertDialog.Description

An accessible description to be announced when the dialog is opened. Renders as a div by default, but can be changed to any other element using the asChild prop.

Alternatively, pass aria-describedby to AlertDialogContent and exclude this component.

Radix Dialog.Description props.

AlertDialog.Action

A button that confirms the Alert Dialog action. Defaults to appearance="filled", with a tone derived from AlertDialog.Root's intent: "danger" dialogs get a danger button, "info" dialogs get a neutral one. Does not close the alert dialog by default.

These buttons should be distinguished visually from AlertDialog.Cancel.

Composes around the mantle Button component. All Button props, except appearance and intent, which are optional here — when omitted, appearance defaults to "filled" and intent derives from the dialog's intent as described above.

AlertDialog.Cancel

A button that closes the dialog and cancels the action. Defaults to appearance="outlined" and intent="neutral".

This button should be distinguished visually from AlertDialog.Action.

Composes around the mantle Button component. All Button props, except appearance and intent, which are optional here — when omitted, they default to "outlined" and "neutral" as described above.

AlertDialog.Icon

An icon that visually represents the intent of the AlertDialog. Defaults to a warning icon for danger intent and an info icon for info intent with the appropriate color. The icon is decorative and renders aria-hidden="true" unless you name it.

All props from svg except children, plus:

PropTypeDefaultDescription
svg?ReactNodeAn icon derived from intentA single SVG icon element that renders in place of the default icon.

AlertDialog.Close

A button that closes the dialog.

All props from button, plus:

PropTypeDefaultDescription
asChild?booleanfalseUse the asChild prop to compose the AlertDialog.Close styling onto alternative element types or your own React components.