Alert Dialog
A modal that demands a response before anything else can happen.
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"),
),
),
);
}An alert dialog interrupts the page with a question the user has to answer — confirm a deletion, discard unsaved changes. It shares its machinery with Dialog: AlertDialog is the root, AlertDialogTrigger opens it, AlertDialogContent is the panel. Instead of a generic close, it has two ends: AlertDialogCancel backs out and AlertDialogAction confirms.
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
} from "@implementjs/primitives";
AlertDialog(
AlertDialogTrigger("Delete account"),
AlertDialogPortal(
AlertDialogOverlay(),
AlertDialogContent(
AlertDialogTitle("Are you absolutely sure?"),
AlertDialogDescription("This action cannot be undone."),
AlertDialogCancel("Cancel"),
AlertDialogAction({ onClick: () => deleteAccount() }, "Delete account"),
),
),
);
Each part accepts optional props and children — pass a props object when you need attributes, or pass children directly. See createComponent. Extra props are forwarded onto the underlying Button, Div, H2, or P.
How it differs from Dialog
Everything from Dialog — open state, multiple triggers, title and description wiring, the portal, nesting and stacks — works the same here. Three things change:
- The panel is
role="alertdialog"instead ofrole="dialog", so screen readers announce it as needing a response. - Clicking outside does not dismiss it. The user has to pick an answer (or press Escape, which still cancels).
- When it opens, focus lands on
AlertDialogCancelinstead of the first focusable element, so Enter or Space backs out rather than confirming the destructive thing.
Use a plain dialog for forms and detail views the user can wander out of; use an alert dialog when leaving without answering would lose something.
Cancel and action
AlertDialogCancel and AlertDialogAction are both Buttons that close the dialog. The difference is intent: put the work on the action's onClick, and keep the cancel consequence-free.
AlertDialogContent(
AlertDialogTitle("Discard draft?"),
AlertDialogCancel("Keep editing"),
AlertDialogAction({ onClick: () => discard() }, "Discard"),
);
The cancel button receives focus when the dialog opens. If you leave it out, focus falls back to the first focusable element in the panel, so consider what ends up under the user's finger.
Open state
AlertDialog owns whether the panel is open. Pass a boolean to seed it, or a signal to control it from outside:
const open = signal(false);
AlertDialog({ open }, AlertDialogTrigger("Delete"), AlertDialogContent("Sure?"));
Button({ onClick: () => open.set(false) }, "Close");
Closing returns focus to the trigger that opened it, or the one marked default.
Styling
Trigger, overlay, and content expose data-state as "open" or "closed"; cancel and action expose data-alert-dialog-cancel and data-alert-dialog-action. Overlay and content stay in the tree while closed; hide them with CSS and center the panel with fixed and a transform, exactly like Dialog.
AlertDialogOverlay({
class: "fixed inset-0 z-50 bg-black/50 data-[state=closed]:hidden data-[state=closed]:opacity-0",
});
AlertDialogContent(
{
class:
"fixed top-1/2 left-1/2 z-50 w-full max-w-lg -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-6 shadow-lg data-[state=closed]:hidden data-[state=closed]:scale-95",
},
AlertDialogTitle({ class: "text-lg font-semibold" }, "Are you absolutely sure?"),
AlertDialogDescription({ class: "text-sm text-muted-foreground" }, "This cannot be undone."),
);
API Reference
AlertDialog
The root. Owns whether the alert dialog is open and provides that to the parts inside it.
| Prop | Type | Default | Description |
|---|---|---|---|
open | Signal<boolean> | boolean | false | The open state. Pass a signal to control it from outside; a boolean seeds uncontrolled state. |
preventScroll | boolean | true | When 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.
| Prop | Type | Default | Description |
|---|---|---|---|
default | boolean | false | When the alert dialog starts open, return focus to this trigger instead of the first one in the tree. |
| Data attribute | Value |
|---|---|
[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. Renders a Div; extra props are forwarded onto it.
| Data attribute | Value |
|---|---|
[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 variable | Description |
|---|---|
--ip-nested-count | How 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-level | This 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. Renders a Div; extra props are forwarded onto it.
| Data attribute | Value |
|---|---|
[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 variable | Description |
|---|---|
--ip-nested-count | How 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-level | This 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 attribute | Value |
|---|---|
[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 attribute | Value |
|---|---|
[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.
| Prop | Type | Default | Description |
|---|---|---|---|
to | HTMLElement | Readable<HTMLElement> | document.body | The element to mount into. Also available as chained .To(target). |
disabled | boolean | Readable<boolean> | false | Mount 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.
| Data attribute | Value |
|---|---|
[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.
| Data attribute | Value |
|---|---|
[data-alert-dialog-action] | Present |