Alert Dialog
A shadcn-style alert dialog component built with Ark UI primitives.
Delete project?
This action cannot be undone.
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
const AlertDialogDemo = () => (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="outline">
Alert Dialog
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete project?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button size="sm" variant="ghost">
Cancel
</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button size="sm" variant="destructive">
Delete
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
export default AlertDialogDemo;
Installation
npx shadcn@latest add @ark-cn/alert-dialogInstall the dependency required by this primitive:
npm install @ark-ui/react class-variance-authorityCopy the component source into your app:
TSXcomponents/ui/alert-dialog.tsx
"use client";
import { Dialog } from "@ark-ui/react/dialog";
import { ark } from "@ark-ui/react/factory";
import { Portal } from "@ark-ui/react/portal";
import { cva, type VariantProps } from "class-variance-authority";
import { type ComponentProps, forwardRef } from "react";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export const AlertDialog = ({
closeOnInteractOutside = false,
role = "alertdialog",
...props
}: Dialog.RootProps) => (
<Dialog.Root
role={role}
closeOnInteractOutside={closeOnInteractOutside}
{...props}
/>
);
export const AlertDialogTrigger = ({
className,
...props
}: Dialog.TriggerProps) => (
<Dialog.Trigger className={cn(className)} {...props} />
);
export const AlertDialogPortal = ({
...props
}: ComponentProps<typeof Portal>) => <Portal {...props} />;
export const AlertDialogBackdrop = ({
className,
...props
}: Dialog.BackdropProps) => (
<Dialog.Backdrop
className={cn(
"fixed inset-0 z-50 bg-black/40 backdrop-blur-sm transition-opacity duration-200 dark:bg-black/50 dark:backdrop-blur-md data-[state=closed]:opacity-0 data-[state=open]:opacity-100 supports-backdrop-filter:bg-black/25 supports-backdrop-filter:dark:bg-black/35",
className,
)}
{...props}
/>
);
export const AlertDialogOverlay = AlertDialogBackdrop;
const positionerVariants = cva("fixed inset-0 z-50 flex overscroll-y-none ", {
defaultVariants: {
bottomStick: true,
},
variants: {
bottomStick: {
false: "items-center justify-center p-4",
true: "items-end justify-center sm:items-center",
},
},
});
const contentVariants = cva(
"relative z-50 grid min-h-0 max-h-[min(calc(100dvh-2rem),var(--alert-dialog-max-h,32rem))] w-full shrink-0 gap-4 overflow-x-hidden overflow-y-auto rounded-xl border border-border bg-popover px-6 pt-6 pb-0 text-card-foreground shadow-lg outline-none transition-[opacity,transform] duration-150 data-[state=closed]:scale-[0.98] data-[state=closed]:opacity-0 data-[state=open]:scale-100 data-[state=open]:opacity-100",
{
defaultVariants: {
size: "default",
},
variants: {
size: {
default: "max-w-lg",
sm: "max-w-sm gap-3 px-5 pt-5 pb-0 text-sm **:data-[slot=alert-dialog-title]:text-base",
},
},
},
);
export type AlertDialogPopupProps = Dialog.ContentProps & {
backdropClassName?: string;
bottomStickOnMobile?: boolean;
positionerClassName?: string;
size?: VariantProps<typeof contentVariants>["size"];
};
const AlertDialogPopupInner = forwardRef<HTMLDivElement, AlertDialogPopupProps>(
(
{
backdropClassName,
bottomStickOnMobile = true,
children,
className,
positionerClassName,
size = "default",
...contentProps
},
ref,
) => (
<Portal>
<AlertDialogBackdrop className={backdropClassName} />
<Dialog.Positioner
className={cn(
positionerVariants({ bottomStick: bottomStickOnMobile }),
positionerClassName,
)}
>
<Dialog.Content
ref={ref}
className={cn(contentVariants({ size }), className)}
{...contentProps}
>
{children}
</Dialog.Content>
</Dialog.Positioner>
</Portal>
),
);
export const AlertDialogPopup = AlertDialogPopupInner;
export const AlertDialogContent = AlertDialogPopup;
export const AlertDialogHeader = ({
className,
...props
}: ComponentProps<typeof ark.div>) => (
<ark.div
className={cn("flex flex-col gap-2 text-center sm:text-start", className)}
{...props}
/>
);
const footerVariants = cva(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:gap-2",
{
defaultVariants: {
variant: "default",
},
variants: {
variant: {
bare: "pb-6",
default:
"-mx-6 mt-2 border-border border-t bg-muted/40 px-6 pt-4 pb-6 sm:rounded-b-xl",
},
},
},
);
export const AlertDialogFooter = ({
className,
variant,
...props
}: ComponentProps<"div"> & VariantProps<typeof footerVariants>) => (
<ark.div className={cn(footerVariants({ variant }), className)} {...props} />
);
export const AlertDialogTitle = ({
className,
...props
}: Dialog.TitleProps) => (
<Dialog.Title
className={cn("font-semibold text-lg leading-none", className)}
{...props}
/>
);
export const AlertDialogDescription = ({
className,
...props
}: Dialog.DescriptionProps) => (
<Dialog.Description
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
export const AlertDialogMedia = ({
className,
...props
}: ComponentProps<typeof ark.div>) => (
<ark.div
className={cn(
"mx-auto flex size-12 shrink-0 items-center justify-center rounded-full bg-muted sm:mx-0 [&_svg]:size-6 [&_svg]:text-muted-foreground",
className,
)}
{...props}
/>
);
export const AlertDialogPanel = ({
className,
...props
}: ComponentProps<typeof ark.div>) => (
<ark.div className={cn("flex w-full flex-col gap-3", className)} {...props} />
);
export const AlertDialogClose = ({
className,
...props
}: Dialog.CloseTriggerProps) => (
<Dialog.CloseTrigger
className={cn(
"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
className,
)}
{...props}
/>
);
export const AlertDialogCancel = ({
className,
variant = "ghost",
...props
}: Dialog.CloseTriggerProps & {
variant?: VariantProps<typeof buttonVariants>["variant"];
}) => (
<Dialog.CloseTrigger
className={cn(buttonVariants({ size: "default", variant }), className)}
{...props}
/>
);
export const AlertDialogAction = ({
className,
variant = "default",
...props
}: Dialog.CloseTriggerProps & {
variant?: VariantProps<typeof buttonVariants>["variant"];
}) => (
<Dialog.CloseTrigger
className={cn(buttonVariants({ size: "default", variant }), className)}
{...props}
/>
);
export const AlertDialogRootProvider = ({
...props
}: Dialog.RootProviderProps) => <Dialog.RootProvider {...props} />;
export {
useDialog as useAlertDialog,
useDialog,
useDialogContext as useAlertDialogContext,
} from "@ark-ui/react/dialog";
Update import aliases to match your project setup.
Usage
import * as AlertDialog from "@/components/ui/alert-dialog"Read exported parts in src/components/ui/alert-dialog.tsx and compose the primitive according to the Ark UI pattern for this component.
Examples
Alert Dialog
Delete project?
This action cannot be undone.
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
const AlertDialogDemo = () => (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="outline">
Alert Dialog
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete project?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button size="sm" variant="ghost">
Cancel
</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button size="sm" variant="destructive">
Delete
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
export default AlertDialogDemo;
Alert Dialog with Bare Footer
Alert Dialog with Bare Footer
Use a simplified footer for lighter confirmation flows.
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
const AlertDialogBareFooter = () => (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="outline">
Alert Dialog with Bare Footer
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Alert Dialog with Bare Footer</AlertDialogTitle>
<AlertDialogDescription>
Use a simplified footer for lighter confirmation flows.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter variant="bare">
<AlertDialogCancel asChild>
<Button size="sm" variant="ghost">
Not now
</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button size="sm">Proceed</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
export default AlertDialogBareFooter;
Close Confirmation
Close without saving?
You have unsaved changes in this form. Closing now will discard them.
import { AlertTriangleIcon } from "lucide-react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
const AlertDialogCloseConfirmation = () => (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="destructive-outline">
Close Confirmation
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia className="bg-destructive/10 text-destructive">
<AlertTriangleIcon className="size-6" />
</AlertDialogMedia>
<AlertDialogTitle>Close without saving?</AlertDialogTitle>
<AlertDialogDescription>
You have unsaved changes in this form. Closing now will discard them.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button size="sm" variant="ghost">
Keep editing
</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button size="sm" variant="destructive">
Close anyway
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
export default AlertDialogCloseConfirmation;
API reference
This component mirrors the upstream Ark UI primitive. All props and DOM behavior are defined by Ark unless you see an ark-cn-only row below.
AlertDialogPopup
| Prop | Type | Description |
|---|---|---|
| backdropClassName? | string | Extra class for the backdrop element. |
| bottomStickOnMobile? | boolean | Bottom-aligned positioner on small screens instead of centered. |
| positionerClassName? | string | Extra class on the positioner wrapper. |
| size? | "default" | "sm" | Preset max-width for the content shell. |
AlertDialogFooter
| Prop | Type | Description |
|---|---|---|
| variant? | "default" | "bare" | Bordered/muted footer bar vs minimal padding only. |
AlertDialogCancel
| Prop | Type | Description |
|---|---|---|
| variant? | ButtonVariant | Button style preset for the cancel trigger (default ghost). |
AlertDialogAction
| Prop | Type | Description |
|---|---|---|
| variant? | ButtonVariant | Button style preset for the action trigger. |
See the ARK UI documentation for the full API.