Alert Dialog
A modal dialog that interrupts the user with important content and expects a response. Typically used for confirmations and destructive actions.
Docsimport {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogCancel,
AlertDialogAction,
} from "~/components/alert-dialog";
export default function AlertDialogBasicDemo() {
return (
<div>
<AlertDialog>
<AlertDialogTrigger>Delete Account</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your account and remove
your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel variant="outline">Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive">Delete Account</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}Installation
CLI
Run the following command to add the component to your project:
npx @ark-preset/cli@latest add alert-dialogManual
Create the recipe file at src/components/recipes/alert-dialog.ts:
import { tv, type VariantProps } from "tailwind-variants";
export const alertDialogVariants = tv({
slots: {
backdrop:
"fixed inset-0 z-50 bg-background/10 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=open]:duration-300 data-[state=closed]:duration-200",
positioner: "fixed inset-0 z-50 flex items-center justify-center",
content:
"relative z-50 grid w-full max-w-lg gap-4 border border-border bg-background p-4 shadow-lg rounded-2xl mx-2 sm:mx-0 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=open]:duration-300 data-[state=closed]:duration-200",
header: "flex flex-col space-y-1.5 text-center sm:text-left",
footer:
"flex flex-col-reverse sm:flex-row sm:justify-end gap-2 bg-muted/50 p-4 -mx-4 -mb-4 rounded-b-2xl border-t border-border",
title: "text-lg font-semibold tracking-tight",
description: "text-sm text-muted-foreground",
closeTrigger:
"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 p-1",
},
});
export type AlertDialogVariants = VariantProps<typeof alertDialogVariants>;Create the component directory and files.
src/components/alert-dialog/alert-dialog.base.tsx:
import { Dialog as ArkDialog } from "@ark-ui/solid/dialog";
import { splitProps, type Component } from "solid-js";
import { alertDialogVariants, buttonVariants, type ButtonVariants } from "../recipes/alert-dialog";
import { HTMLProps } from "@ark-ui/solid";
import { ark, type HTMLArkProps } from "@ark-ui/solid/factory";
const styles = alertDialogVariants();
const AlertDialogRoot = ArkDialog.Root;
const AlertDialogRootProvider = ArkDialog.RootProvider;
const AlertDialogTrigger: Component<ArkDialog.TriggerProps & ButtonVariants> = (props) => {
const [local, others] = splitProps(props, ["class", "variant", "size"]);
return (
<ArkDialog.Trigger
class={buttonVariants({ variant: local.variant, size: local.size, class: local.class })}
{...others}
/>
);
};
const AlertDialogBackdrop: Component<ArkDialog.BackdropProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkDialog.Backdrop class={styles.backdrop({ class: local.class })} {...others} />;
};
const AlertDialogPositioner: Component<ArkDialog.PositionerProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkDialog.Positioner class={styles.positioner({ class: local.class })} {...others} />;
};
const AlertDialogContent: Component<ArkDialog.ContentProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkDialog.Content class={styles.content({ class: local.class })} {...others} />;
};
const AlertDialogCloseTrigger: Component<
HTMLProps<"button"> & ArkDialog.CloseTriggerProps & ButtonVariants
> = (props) => {
const [local, others] = splitProps(props, ["class", "variant", "size"]);
return (
<ArkDialog.CloseTrigger
class={buttonVariants({ variant: local.variant, size: local.size, class: local.class })}
{...others}
/>
);
};
const AlertDialogTitle: Component<ArkDialog.TitleProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkDialog.Title class={styles.title({ class: local.class })} {...others} />;
};
const AlertDialogDescription: Component<ArkDialog.DescriptionProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkDialog.Description class={styles.description({ class: local.class })} {...others} />;
};
const AlertDialogHeader: Component<HTMLArkProps<"div">> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ark.div class={styles.header({ class: local.class })} {...others} />;
};
const AlertDialogFooter: Component<HTMLArkProps<"div">> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ark.div class={styles.footer({ class: local.class })} {...others} />;
};
const AlertDialogAction: Component<HTMLArkProps<"button"> & ButtonVariants> = (props) => {
const [local, others] = splitProps(props, ["class", "variant", "size"]);
return (
<ark.button
type="button"
class={buttonVariants({ variant: local.variant, size: local.size, class: local.class })}
{...others}
/>
);
};
export const AlertDialog = {
Root: AlertDialogRoot,
RootProvider: AlertDialogRootProvider,
Trigger: AlertDialogTrigger,
Backdrop: AlertDialogBackdrop,
Positioner: AlertDialogPositioner,
Content: AlertDialogContent,
CloseTrigger: AlertDialogCloseTrigger,
UnstyledCloseTrigger: ArkDialog.CloseTrigger,
Title: AlertDialogTitle,
Description: AlertDialogDescription,
Header: AlertDialogHeader,
Footer: AlertDialogFooter,
Cancel: AlertDialogCloseTrigger,
Action: AlertDialogAction,
};src/components/alert-dialog/index.tsx:
import { Dialog as ArkDialog } from "@ark-ui/solid/dialog";
import { Portal } from "solid-js/web";
import { splitProps, type Component } from "solid-js";
import { AlertDialog as AlertDialogBase } from "./alert-dialog.base";
const AlertDialogContent: Component<ArkDialog.ContentProps> = (props) => {
const [local, others] = splitProps(props, ["class", "children"]);
return (
<Portal>
<AlertDialogBase.Backdrop />
<AlertDialogBase.Positioner>
<AlertDialogBase.Content class={local.class} {...others}>
{local.children}
</AlertDialogBase.Content>
</AlertDialogBase.Positioner>
</Portal>
);
};
const AlertDialog = AlertDialogBase.Root;
const AlertDialogTrigger = AlertDialogBase.Trigger;
const AlertDialogHeader = AlertDialogBase.Header;
const AlertDialogTitle = AlertDialogBase.Title;
const AlertDialogDescription = AlertDialogBase.Description;
const AlertDialogFooter = AlertDialogBase.Footer;
const AlertDialogCancel = AlertDialogBase.Cancel;
const AlertDialogAction = AlertDialogBase.Action;
const AlertDialogUnstyledCloseTrigger = ArkDialog.CloseTrigger;
export {
AlertDialog,
AlertDialogTrigger,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogContent,
AlertDialogCancel,
AlertDialogAction,
AlertDialogUnstyledCloseTrigger,
AlertDialogBase,
};
export { alertDialogVariants, type AlertDialogVariants } from "../recipes/alert-dialog";Note: Make sure your project has the Tailwind CSS theme variables set up (
--background,--foreground,--ring,--border, etc.) or override the utility classes to match your design system.
Dependencies: This component imports shared recipes or sub-components from other packages. Make sure the following are also installed: button.
Usage
Basic Usage
import {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogCancel,
AlertDialogAction,
} from "~/components/alert-dialog";
export default function AlertDialogBasicDemo() {
return (
<div>
<AlertDialog>
<AlertDialogTrigger>Delete Account</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your account and remove
your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel variant="outline">Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive">Delete Account</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}Advanced Usage
Root Provider
Use AlertDialogBase.RootProvider when you need to control the dialog state from outside its component tree. This pattern uses the useDialog hook from Ark UI and is useful for utility dialogs like delete confirmations that can be triggered from any element.
Open: false
import { useDialog, type UseDialogReturn } from "@ark-ui/solid/dialog";
import {
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogCancel,
AlertDialogAction,
AlertDialogBase,
} from "~/components/alert-dialog";
import { Button } from "~/components/alert-dialog";
import { type Component, createSignal } from "solid-js";
interface DeleteAlertDialogProps {
dialog: UseDialogReturn;
title: string;
description: string;
onDelete: () => void;
}
const DeleteAlertDialog: Component<DeleteAlertDialogProps> = (props) => {
return (
<AlertDialogBase.RootProvider value={props.dialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{props.title}</AlertDialogTitle>
<AlertDialogDescription>{props.description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel variant="outline">Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive" onClick={() => props.onDelete()}>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogBase.RootProvider>
);
};
export default function AlertDialogDeleteDemo() {
const dialog = useDialog({ defaultOpen: false });
const [deleted, setDeleted] = createSignal(false);
const handleDelete = () => {
dialog().setOpen(false);
setDeleted(true);
};
return (
<div class="space-y-4">
<p class="text-sm text-muted-foreground">Open: {JSON.stringify(dialog().open)}</p>
<Button onClick={() => dialog().setOpen(true)} variant="destructive" disabled={deleted()}>
{deleted() ? "Account Deleted" : "Delete Account"}
</Button>
{deleted() && <p class="text-sm text-destructive">Account has been deleted. (Demo only)</p>}
<DeleteAlertDialog
dialog={dialog}
title="Delete Account"
description="This will permanently delete your account and remove your data from our servers."
onDelete={handleDelete}
/>
</div>
);
}The key difference:
AlertDialog— manages its own state internally. Use for simple, self-contained usage.AlertDialogBase.RootProvider— accepts a pre-created context viauseDialog. Use when you need to read or control the dialog state from outside the component tree. Ideal for reusable utility dialogs.
API Reference
See the Ark UI Dialog documentation.
The AlertDialog automatically sets role="alertdialog" on the dialog content, providing proper accessibility semantics for alert dialogs.
Anatomy
| Part | Element | Description |
|---|---|---|
AlertDialog | — | Manages dialog state, uses role="alertdialog" |
AlertDialogTrigger | button | The button that opens the dialog |
AlertDialogContent | div | The dialog panel (portal to body) |
AlertDialogHeader | div | Header section for title and description |
AlertDialogTitle | h2 | The dialog title |
AlertDialogDescription | div | Descriptive text for the dialog |
AlertDialogFooter | div | Footer section for action buttons |
AlertDialogCancel | button | Dismisses the dialog |
AlertDialogAction | button | Confirms the dialog action |