Alert Dialog

A modal dialog that interrupts the user with important content and expects a response. Typically used for confirmations and destructive actions.

Docs
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>
  );
}

Installation

CLI

Run the following command to add the component to your project:

npx @ark-preset/cli@latest add alert-dialog

Manual

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 via useDialog. 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

PartElementDescription
AlertDialogManages dialog state, uses role="alertdialog"
AlertDialogTriggerbuttonThe button that opens the dialog
AlertDialogContentdivThe dialog panel (portal to body)
AlertDialogHeaderdivHeader section for title and description
AlertDialogTitleh2The dialog title
AlertDialogDescriptiondivDescriptive text for the dialog
AlertDialogFooterdivFooter section for action buttons
AlertDialogCancelbuttonDismisses the dialog
AlertDialogActionbuttonConfirms the dialog action