implement
Alert Dialog

Alert Dialog

A modal that interrupts the user and waits for a deliberate answer.

import { Div } from "@implementjs/core";
import {
	AlertDialog,
	AlertDialogAction,
	AlertDialogCancel,
	AlertDialogContent,
	AlertDialogDescription,
	AlertDialogTitle,
	AlertDialogTrigger,
} from "@/lib/components/ui/alert-dialog";

export default function AlertDialogDemo() {
	return AlertDialog(
		AlertDialogTrigger({ variant: "outline" }, "Delete account"),
		AlertDialogContent(
			Div(
				{ class: "grid gap-1.5" },
				AlertDialogTitle("Are you absolutely sure?"),
				AlertDialogDescription(
					"This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
				),
			),
			Div(
				{ class: "flex justify-end gap-2" },
				AlertDialogCancel("Cancel"),
				AlertDialogAction({ variant: "destructive" }, "Delete account"),
			),
		),
	);
}

Installation

npx jsrepo add @implementjs/ui/alert-dialog

jsrepo pulls button along with it.

Usage

An alert dialog is the dialog with the escape hatches removed: no close button in the corner, and no dismissing by clicking the overlay. The only ways out are AlertDialogCancel and AlertDialogAction, which is the point — the user has to answer.

AlertDialogContent renders its own AlertDialogOverlay inside an AlertDialogPortal, so the scrim is never yours to place. All three buttons take variant and size from the button styles, so a destructive confirmation is one prop.

import {
	AlertDialog,
	AlertDialogAction,
	AlertDialogCancel,
	AlertDialogContent,
	AlertDialogDescription,
	AlertDialogTitle,
	AlertDialogTrigger,
} from "@/lib/components/ui/alert-dialog";

AlertDialog(
	AlertDialogTrigger({ variant: "destructive" }, "Delete account"),
	AlertDialogContent(
		AlertDialogTitle("Are you absolutely sure?"),
		AlertDialogDescription("This permanently deletes your account."),
		AlertDialogCancel("Cancel"),
		AlertDialogAction({ variant: "destructive" }, "Delete account"),
	),
);

Stacking

The overlay and the content read --ip-nested-level and --ip-nested-count, which the primitive sets when dialogs open on top of one another. A nested alert dialog's overlay renders transparent instead of darkening the page a second time, and the dialog underneath scales back and shifts up so the stack stays legible.

API Reference

Every prop the styling does not consume is forwarded to the Alert Dialog primitive, so the tables below are the whole surface — the behavior props and the styling ones together.

AlertDialog

The root. Owns whether the alert dialog is open and provides that to the parts inside it.

PropTypeDefaultDescription
openSignal<boolean> | booleanfalseThe open state. Pass a signal to control it from outside; a boolean seeds uncontrolled state.
preventScrollbooleantrueWhen true, the page behind cannot scroll while the alert dialog is open. The overlay and panel can still scroll if you give them overflow.

AlertDialogTrigger

Toggles the alert dialog open and closed. Clicking a different trigger keeps it open and remembers that button for focus return. Renders a Button; extra props are forwarded onto it.

PropTypeDefaultDescription
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link""default"Which button style the part renders with. Also set as data-variant.
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"The button's height and padding scale. Also set as data-size.
defaultbooleanfalseWhen the alert dialog starts open, return focus to this trigger instead of the first one in the tree.
Data attributeValue
[data-alert-dialog-trigger]Present
[data-state]"open" | "closed"

AlertDialogOverlay

The backdrop behind the panel. Style it against data-state; the primitive does not hide it for you. Styled as a fixed scrim that fades in and out; a nested alert dialog's overlay renders transparent so the stack does not darken twice. AlertDialogContent renders one for you — this export is for composing a panel of your own. Renders a Div; extra props are forwarded onto it.

Data attributeValue
[data-alert-dialog-overlay]Present
[data-state]"open" | "closed"
[data-nested]Present when this dialog is nested in another
[data-nested-open]Present when a nested dialog is open
[data-nested-count]Number of open nested dialogs
[data-nested-level]Depth in the stack; 0 is the outermost dialog
CSS variableDescription
--ip-nested-countHow many nested dialogs are open above this one. Use it to scale or translate the parent in a stack, e.g. scale(calc(1 - 0.05 * var(--ip-nested-count))).
--ip-nested-levelThis dialog's depth in the stack, 0 for the outermost. Raise z-index with it so nested dialogs paint above their parent, e.g. z-index: calc(50 + var(--ip-nested-level)).

AlertDialogContent

The panel. Sets role="alertdialog" and aria-modal. Clicking outside does not dismiss it; Escape still cancels. Style it against data-state; the primitive does not hide or position it for you. Styled as a centered panel that scales in, and shifts up as further dialogs stack on top of it. Renders its own AlertDialogOverlay inside an AlertDialogPortal, so neither has to be placed by hand. Renders a Div; extra props are forwarded onto it.

PropTypeDefaultDescription
overlayAlertDialogOverlayProps{}Props for the overlay the content renders behind itself.
Data attributeValue
[data-alert-dialog-content]Present
[data-state]"open" | "closed"
[data-nested]Present when this dialog is nested in another
[data-nested-open]Present when a nested dialog is open
[data-nested-count]Number of open nested dialogs
[data-nested-level]Depth in the stack; 0 is the outermost dialog
CSS variableDescription
--ip-nested-countHow many nested dialogs are open above this one. Use it to scale or translate the parent in a stack, e.g. scale(calc(1 - 0.05 * var(--ip-nested-count))).
--ip-nested-levelThis dialog's depth in the stack, 0 for the outermost. Raise z-index with it so nested dialogs paint above their parent, e.g. z-index: calc(50 + var(--ip-nested-level)).

AlertDialogTitle

The heading. Put it inside the content. Wires up aria-labelledby on the panel. Renders a H2; extra props are forwarded onto it.

Data attributeValue
[data-alert-dialog-title]Present

AlertDialogDescription

Supporting text. Put it inside the content. Wires up aria-describedby on the panel. Renders a P; extra props are forwarded onto it.

Data attributeValue
[data-alert-dialog-description]Present

AlertDialogPortal

Renders its children into another DOM parent so the overlay and panel escape overflow and stacking. This is the core Portal helper; context still resolves from where the portal is declared.

PropTypeDefaultDescription
toHTMLElement | Readable<HTMLElement>document.bodyThe element to mount into. Also available as chained .To(target).
disabledboolean | Readable<boolean>falseMount in place instead of teleporting. Keep nested dialogs portaled so they stack above the parent. Also available as chained .Disabled(value).

AlertDialogCancel

Closes without confirming. Receives focus when the alert dialog opens, so Enter or Space backs out instead of confirming. Renders a Button; extra props are forwarded onto it.

PropTypeDefaultDescription
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link""outline"Which button style the part renders with. Also set as data-variant.
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"The button's height and padding scale. Also set as data-size.
Data attributeValue
[data-alert-dialog-cancel]Present

AlertDialogAction

Confirms and closes when clicked. Attach the actual work to onClick. Renders a Button; extra props are forwarded onto it.

PropTypeDefaultDescription
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link""default"Which button style the part renders with. Also set as data-variant.
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"The button's height and padding scale. Also set as data-size.
Data attributeValue
[data-alert-dialog-action]Present

Are you absolutely sure?

This action cannot be undone. This will permanently delete your account and remove your data from our servers.