AlertDialog
Client leafA modal that interrupts to confirm a consequential action.
Live preview
Rendered from the installed VEXA source — the same files you receive.
Installation
Adds the component source to your project. Registry dependencies are pulled in automatically.
npx shadcn@latest add https://vexa.valfiguer.com/r/alert-dialog.jsonInstalls to components/vexa/alert-dialog.tsx
npm dependencies
- @radix-ui/react-alert-dialog
Registry dependencies
- @vexa/utils
- @vexa/button
Other VEXA items this component needs. Install them first — they are resolved automatically by the CLI.
Source
Copy this into its target path, or install it with the command above.
components/vexa/alert-dialog.tsx
"use client";
import type * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/vexa/button";
/**
* AlertDialog — a modal that interrupts to confirm a consequential or
* destructive action.
*
* Client leaf. Unlike `Dialog`, it is `role="alertdialog"`, cannot be dismissed
* by clicking the backdrop, and requires an explicit choice via
* `AlertDialogAction` / `AlertDialogCancel`. Radix handles focus trapping,
* Escape-to-cancel, and focus return (§14.2). Give it a title and a description
* that names the consequence.
*/
export function AlertDialog(
props: React.ComponentProps<typeof AlertDialogPrimitive.Root>,
) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
}
export function AlertDialogTrigger(
props: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>,
) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
);
}
export function AlertDialogPortal(
props: React.ComponentProps<typeof AlertDialogPrimitive.Portal>,
) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
);
}
export function AlertDialogOverlay({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return (
<AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn(
"fixed inset-0 z-50 bg-vexa-background/70 backdrop-blur-sm",
"transition-opacity duration-[var(--vexa-duration-base)] ease-[var(--vexa-ease-standard)] motion-reduce:transition-none",
"data-[state=closed]:opacity-0 data-[state=open]:opacity-100",
className,
)}
{...props}
/>
);
}
export function AlertDialogContent({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
data-slot="alert-dialog-content"
className={cn(
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4",
"rounded-vexa-lg border border-vexa-border bg-vexa-surface-raised p-6 text-vexa-foreground shadow-vexa-lg",
"transition-[opacity,transform] duration-[var(--vexa-duration-base)] ease-[var(--vexa-ease-standard)] motion-reduce:transition-none",
"data-[state=closed]:scale-95 data-[state=closed]:opacity-0 data-[state=open]:scale-100 data-[state=open]:opacity-100",
"focus-visible:outline-none",
className,
)}
{...props}
/>
</AlertDialogPortal>
);
}
export function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-header"
className={cn("flex flex-col gap-1.5 text-left", className)}
{...props}
/>
);
}
export function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className,
)}
{...props}
/>
);
}
export function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
);
}
export function AlertDialogDescription({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn("text-sm text-vexa-muted-foreground", className)}
{...props}
/>
);
}
export function AlertDialogAction({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
return (
<AlertDialogPrimitive.Action
data-slot="alert-dialog-action"
className={cn(buttonVariants({ variant: "primary" }), className)}
{...props}
/>
);
}
export function AlertDialogCancel({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
return (
<AlertDialogPrimitive.Cancel
data-slot="alert-dialog-cancel"
className={cn(buttonVariants({ variant: "outline" }), className)}
{...props}
/>
);
}
Details
Example
tsx
<AlertDialog><AlertDialogTrigger asChild><Button variant="danger">Delete</Button></AlertDialogTrigger><AlertDialogContent><AlertDialogHeader><AlertDialogTitle>Delete project?</AlertDialogTitle><AlertDialogDescription>This cannot be undone.</AlertDialogDescription></AlertDialogHeader><AlertDialogFooter><AlertDialogCancel>Cancel</AlertDialogCancel><AlertDialogAction>Delete</AlertDialogAction></AlertDialogFooter></AlertDialogContent></AlertDialog>Accessibility
role=alertdialog; not dismissible by clicking the backdrop and requires an explicit Action/Cancel choice. Focus is trapped, Escape cancels, and focus returns to the trigger. Actions reuse buttonVariants.