---
title: Alert Dialog
description: Ask for confirmation before a consequential action.
group: Components
order: 12
---

Use a modal prompt when an action deserves one last check. It includes a portal overlay, Escape dismissal, and controlled or uncontrolled open state.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"

export function Example() {
  return (
    <AlertDialog>
      <AlertDialogTrigger>Ship update</AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Ship this docs update?</AlertDialogTitle>
          <AlertDialogDescription>
            The changelog entry and component pages go live for everyone on the
            current namespace.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Not yet</AlertDialogCancel>
          <AlertDialogAction>Ship</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add alert-dialog
```

## Usage

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
```

```tsx
<AlertDialog>
  <AlertDialogTrigger>Publish draft</AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Publish this draft?</AlertDialogTitle>
      <AlertDialogDescription>
        This page will be visible to everyone in the current namespace.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Keep editing</AlertDialogCancel>
      <AlertDialogAction>Publish</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>
```

## Composition

```tree
AlertDialog
├── AlertDialogTrigger
└── AlertDialogContent
    ├── AlertDialogHeader
    │   ├── AlertDialogMedia
    │   ├── AlertDialogTitle
    │   └── AlertDialogDescription
    └── AlertDialogFooter
        ├── AlertDialogCancel
        └── AlertDialogAction
```

`AlertDialogMedia` is optional. When it contains an icon, the title and description sit beside it.

## Basic

The trigger opens the dialog. Cancel closes it, while the action confirms the choice.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"

export function Example() {
  return (
    <AlertDialog>
      <AlertDialogTrigger>Ship update</AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Ship this docs update?</AlertDialogTitle>
          <AlertDialogDescription>
            The changelog entry and component pages go live for everyone on the
            current namespace.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Not yet</AlertDialogCancel>
          <AlertDialogAction>Ship</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Small

Set `size="sm"` on `AlertDialogContent` for a more compact panel.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"

export function Example() {
  return (
    <AlertDialog>
      <AlertDialogTrigger>Discard edits</AlertDialogTrigger>
      <AlertDialogContent size="sm">
        <AlertDialogHeader>
          <AlertDialogTitle>Discard local edits?</AlertDialogTitle>
          <AlertDialogDescription>
            Unsaved markdown changes in this tab will be dropped.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Keep</AlertDialogCancel>
          <AlertDialogAction>Discard</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Outline

Set `variant="outline"` on `AlertDialogContent` for a bordered panel.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"

export function Example() {
  return (
    <AlertDialog>
      <AlertDialogTrigger>Ship update</AlertDialogTrigger>
      <AlertDialogContent variant="outline">
        <AlertDialogHeader>
          <AlertDialogTitle>Ship this docs update?</AlertDialogTitle>
          <AlertDialogDescription>
            The changelog entry and component pages go live for everyone on the
            current namespace.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Not yet</AlertDialogCancel>
          <AlertDialogAction>Ship</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## With icon

Use `AlertDialogMedia` for a leading icon with the copy beside it.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"

export function Example() {
  return (
    <AlertDialog>
      <AlertDialogTrigger>Pull component</AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogMedia>
            <PackageIcon />
          </AlertDialogMedia>
          <AlertDialogTitle>Add alert to this repo?</AlertDialogTitle>
          <AlertDialogDescription>
            Files write into src/components/ui. You own them after install —
            no package lock-in.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Add</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Small with icon

Combine `size="sm"` with `AlertDialogMedia`.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"

export function Example() {
  return (
    <AlertDialog>
      <AlertDialogTrigger>Revert theme</AlertDialogTrigger>
      <AlertDialogContent size="sm">
        <AlertDialogHeader>
          <AlertDialogMedia>
            <WarningIcon />
          </AlertDialogMedia>
          <AlertDialogTitle>Revert theme tokens?</AlertDialogTitle>
          <AlertDialogDescription>
            Custom CSS variables snap back to the starter defaults.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Revert</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## With image

`AlertDialogMedia` can span the full width to place a preview image above the copy.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"

export function Example() {
  return (
    <AlertDialog>
      <AlertDialogTrigger>Open preview</AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogMedia className="col-span-full mb-3 h-44 w-full overflow-hidden rounded-sm p-0 sm:h-52">
            <img
              src="/assets/brand/demos/alert-dialog-preview.png"
              alt=""
            />
          </AlertDialogMedia>
          <AlertDialogTitle className="!col-start-1">
            Open staged preview?
          </AlertDialogTitle>
          <AlertDialogDescription className="!col-start-1">
            Share a temporary build of the docs. The link expires after seven
            days.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Close</AlertDialogCancel>
          <AlertDialogAction>Open</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Delete confirm

A destructive confirmation with a clear icon and action.

```tsx
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"

export function Example() {
  return (
    <AlertDialog>
      <AlertDialogTrigger variant="destructive">
        Delete demo
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogMedia className="bg-destructive/10 text-destructive">
            <TrashIcon />
          </AlertDialogMedia>
          <AlertDialogTitle>Delete this demo scene?</AlertDialogTitle>
          <AlertDialogDescription>
            The preview and its code sample leave the docs page. You can add
            the scene again later from the registry.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction variant="destructive">Delete</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## Type to confirm

Require the user to enter matching text before enabling a destructive action.

```tsx
import { useState } from "react"
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"

const CONFIRM_TEXT = "drop-namespace"

export function Example() {
  const [value, setValue] = useState("")

  return (
    <AlertDialog
      onOpenChange={(open) => {
        if (!open) setValue("")
      }}
    >
      <AlertDialogTrigger variant="destructive">
        Drop namespace
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Drop @arctis permanently?</AlertDialogTitle>
          <AlertDialogDescription>
            Installs that point at this namespace will fail. Type{" "}
            <span className="font-medium text-foreground">{CONFIRM_TEXT}</span>{" "}
            to continue.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <input
          value={value}
          onChange={(event) => setValue(event.target.value)}
          placeholder={CONFIRM_TEXT}
          className="mt-4 w-full rounded-sm border border-input bg-background px-3 py-2 text-sm outline-none"
        />
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction
            variant="destructive"
            disabled={value !== CONFIRM_TEXT}
          >
            Drop
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
```

## API Reference

### AlertDialog

| Prop | Type | Default |
| --- | --- | --- |
| open | boolean | — |
| defaultOpen | boolean | false |
| onOpenChange | (open: boolean) => void | — |

```tsx
<AlertDialog>
  <AlertDialogTrigger variant="outline">Open</AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogTitle>Are you sure?</AlertDialogTitle>
    <AlertDialogDescription>This cannot be undone.</AlertDialogDescription>
    <AlertDialogCancel>Cancel</AlertDialogCancel>
    <AlertDialogAction>Continue</AlertDialogAction>
  </AlertDialogContent>
</AlertDialog>
```

### AlertDialogTrigger

Renders a [Button](/docs/components/button) and accepts the same props, including `variant`, `size`, and `loading`.

| Prop | Type | Default |
| --- | --- | --- |
| variant | Button variant | "default" |
| size | Button size | "sm" |
| className | string | — |

```tsx
<AlertDialogTrigger variant="outline">Open</AlertDialogTrigger>
```
### AlertDialogContent

| Prop | Type | Default |
| --- | --- | --- |
| size | "default" \| "sm" | "default" |
| variant | "default" \| "outline" | "default" |
| className | string | — |

```tsx
<AlertDialogContent>
  <AlertDialogTitle>Are you sure?</AlertDialogTitle>
  <AlertDialogDescription>This cannot be undone.</AlertDialogDescription>
</AlertDialogContent>
```

### AlertDialogMedia

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<AlertDialogMedia>
  <Icon />
</AlertDialogMedia>
```

### AlertDialogTitle

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
```

### AlertDialogDescription

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<AlertDialogDescription>
  This cannot be undone.
</AlertDialogDescription>
```

### AlertDialogCancel

| Prop | Type | Default |
| --- | --- | --- |
| className | string | — |

```tsx
<AlertDialogCancel>Cancel</AlertDialogCancel>
```

### AlertDialogAction

| Prop | Type | Default |
| --- | --- | --- |
| variant | "default" \| "destructive" | "default" |
| className | string | — |
| disabled | boolean | false |

```tsx
<AlertDialogAction>Continue</AlertDialogAction>
```

## Source

```tsx
"use client";

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useId,
  useMemo,
  useState,
  type ComponentProps,
  type HTMLAttributes,
  type ReactNode,
} from "react";
import { createPortal } from "react-dom";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useBodyScrollLock } from "@/lib/use-body-scroll-lock";
import { useOverlayEntered } from "@/lib/use-overlay-entered";

type AlertDialogContextValue = {
  open: boolean;
  setOpen: (open: boolean) => void;
  titleId: string;
  descriptionId: string;
};

const AlertDialogContext = createContext<AlertDialogContextValue | null>(null);

function useAlertDialog() {
  const context = useContext(AlertDialogContext);
  if (!context) {
    throw new Error("AlertDialog parts must be used within <AlertDialog>");
  }
  return context;
}

type AlertDialogProps = {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean) => void;
  children: ReactNode;
};

export function AlertDialog({
  open: openProp,
  defaultOpen = false,
  onOpenChange,
  children,
}: AlertDialogProps) {
  const [uncontrolled, setUncontrolled] = useState(defaultOpen);
  const controlled = openProp !== undefined;
  const open = controlled ? openProp : uncontrolled;
  const titleId = useId();
  const descriptionId = useId();

  const setOpen = useCallback(
    (next: boolean) => {
      if (!controlled) setUncontrolled(next);
      onOpenChange?.(next);
    },
    [controlled, onOpenChange],
  );

  const value = useMemo(
    () => ({ open, setOpen, titleId, descriptionId }),
    [open, setOpen, titleId, descriptionId],
  );

  return (
    <AlertDialogContext.Provider value={value}>
      {children}
    </AlertDialogContext.Provider>
  );
}

type AlertDialogTriggerProps = ComponentProps<typeof Button>;

export function AlertDialogTrigger({
  className,
  children,
  onClick,
  size = "sm",
  ...props
}: AlertDialogTriggerProps) {
  const { setOpen } = useAlertDialog();

  return (
    <Button
      type="button"
      size={size}
      className={className}
      onClick={(event) => {
        onClick?.(event);
        if (!event.defaultPrevented) setOpen(true);
      }}
      {...props}
    >
      {children}
    </Button>
  );
}

type AlertDialogContentProps = HTMLAttributes<HTMLDivElement> & {
  size?: "default" | "sm";
  variant?: "default" | "outline";
  children: ReactNode;
};

export function AlertDialogContent({
  size = "default",
  variant = "default",
  className,
  children,
  ...props
}: AlertDialogContentProps) {
  const { open, setOpen, titleId, descriptionId } = useAlertDialog();
  const [portalReady, setPortalReady] = useState(false);
  const entered = useOverlayEntered(open);

  useBodyScrollLock(open);

  useEffect(() => {
    setPortalReady(true);
  }, []);

  useEffect(() => {
    if (!open) return;

    function onKeyDown(event: KeyboardEvent) {
      if (event.key === "Escape") setOpen(false);
    }

    document.addEventListener("keydown", onKeyDown);
    return () => {
      document.removeEventListener("keydown", onKeyDown);
    };
  }, [open, setOpen]);

  if (!portalReady || !open) return null;

  return createPortal(
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
      <div
        aria-hidden="true"
        className={cn(
          "arctis-overlay-backdrop absolute inset-0 bg-black/40",
          entered && "arctis-overlay-backdrop-open",
        )}
        onClick={() => setOpen(false)}
      />
      <div
        role="alertdialog"
        aria-modal="true"
        aria-labelledby={titleId}
        aria-describedby={descriptionId}
        data-size={size}
        data-variant={variant}
        className={cn(
          "arctis-overlay relative z-10 w-full origin-center rounded-md border border-border bg-card p-5 text-card-foreground",
          size === "sm" ? "max-w-sm p-4" : "max-w-md",
          entered && "arctis-overlay-open",
          className,
        )}
        {...props}
      >
        {children}
      </div>
    </div>,
    document.body,
  );
}

type AlertDialogHeaderProps = HTMLAttributes<HTMLDivElement> & {
  children: ReactNode;
};

export function AlertDialogHeader({
  className,
  children,
  ...props
}: AlertDialogHeaderProps) {
  return (
    <div
      className={cn(
        "grid gap-y-1 has-[[data-slot=alert-dialog-media]]:grid-cols-[auto_1fr] has-[[data-slot=alert-dialog-media]]:gap-x-3",
        className,
      )}
      {...props}
    >
      {children}
    </div>
  );
}

type AlertDialogMediaProps = HTMLAttributes<HTMLDivElement> & {
  children: ReactNode;
};

export function AlertDialogMedia({
  className,
  children,
  ...props
}: AlertDialogMediaProps) {
  return (
    <div
      data-slot="alert-dialog-media"
      className={cn(
        "peer col-start-1 row-span-2 row-start-1 flex size-10 shrink-0 items-center justify-center self-start rounded-sm bg-background text-foreground [&>img]:size-full [&>img]:rounded-sm [&>img]:object-cover [&>svg]:size-5",
        className,
      )}
      {...props}
    >
      {children}
    </div>
  );
}

type AlertDialogTitleProps = HTMLAttributes<HTMLHeadingElement> & {
  children: ReactNode;
};

export function AlertDialogTitle({
  className,
  children,
  ...props
}: AlertDialogTitleProps) {
  const { titleId } = useAlertDialog();

  return (
    <h2
      id={titleId}
      className={cn(
        "text-base font-medium tracking-tight peer:col-start-2",
        className,
      )}
      {...props}
    >
      {children}
    </h2>
  );
}

type AlertDialogDescriptionProps = HTMLAttributes<HTMLParagraphElement> & {
  children: ReactNode;
};

export function AlertDialogDescription({
  className,
  children,
  ...props
}: AlertDialogDescriptionProps) {
  const { descriptionId } = useAlertDialog();

  return (
    <p
      id={descriptionId}
      className={cn("text-sm text-muted-foreground peer:col-start-2", className)}
      {...props}
    >
      {children}
    </p>
  );
}

type AlertDialogFooterProps = HTMLAttributes<HTMLDivElement> & {
  children: ReactNode;
};

export function AlertDialogFooter({
  className,
  children,
  ...props
}: AlertDialogFooterProps) {
  return (
    <div
      className={cn(
        "mt-5 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
        className,
      )}
      {...props}
    >
      {children}
    </div>
  );
}

type AlertDialogCancelProps = ComponentProps<typeof Button>;

export function AlertDialogCancel({
  className,
  children,
  onClick,
  variant = "secondary",
  size = "sm",
  ...props
}: AlertDialogCancelProps) {
  const { setOpen } = useAlertDialog();

  return (
    <Button
      type="button"
      variant={variant}
      size={size}
      className={className}
      onClick={(event) => {
        onClick?.(event);
        if (!event.defaultPrevented) setOpen(false);
      }}
      {...props}
    >
      {children}
    </Button>
  );
}

type AlertDialogActionProps = ComponentProps<typeof Button>;

export function AlertDialogAction({
  variant = "default",
  className,
  children,
  onClick,
  size = "sm",
  ...props
}: AlertDialogActionProps) {
  const { setOpen } = useAlertDialog();

  return (
    <Button
      type="button"
      variant={variant}
      size={size}
      className={className}
      onClick={(event) => {
        onClick?.(event);
        if (!event.defaultPrevented) setOpen(false);
      }}
      {...props}
    >
      {children}
    </Button>
  );
}
```
