Focus-managed confirmation for consequential actions, with a clear cancel path, optional media, and a token-based destructive action.
Launch workspace
12 files · updated today
"use client";
import * as React from "react";
import { Trash2, FolderClosed } from "lucide-react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ballmac/alert-dialog";
export default function AlertDialogDemo() {
const [removed, setRemoved] = React.useState(false);
return (
<div className="w-full max-w-sm rounded-xl border bg-card p-5 shadow-sm">
<div className="mb-4 flex items-center gap-3">
<span className="flex size-10 items-center justify-center rounded-lg bg-muted">
<FolderClosed aria-hidden="true" className="size-5" />
</span>
<div className="min-w-0">
<p className="text-sm font-semibold">Launch workspace</p>
<p className="text-xs text-muted-foreground">
12 files · updated today
</p>
</div>
</div>
<AlertDialog>
<AlertDialogTrigger
disabled={removed}
className="inline-flex h-9 items-center gap-2 rounded-md border border-destructive/30 px-3 text-sm font-medium text-destructive outline-none hover:bg-destructive/10 focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50"
>
<Trash2 aria-hidden="true" className="size-4" />
{removed ? "Project removed" : "Remove project"}
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia>
<Trash2 />
</AlertDialogMedia>
<AlertDialogTitle>Remove Launch workspace?</AlertDialogTitle>
<AlertDialogDescription>
This removes the project and its 12 files for everyone on your
team. This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Keep project</AlertDialogCancel>
<AlertDialogAction destructive onClick={() => setRemoved(true)}>
Remove project
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}Installation
$ pnpm dlx shadcn@latest add @ballmac/alert-dialogInstall the dependencies.
$ pnpm add radix-uiCopy the source into your project.
components/ballmac/alert-dialog.tsx// Ballmac UI: Alert Dialog. https://ui.ballmac.com/components/alert-dialog "use client"; import * as React from "react"; import { AlertDialog as Primitive } from "radix-ui"; import { cn } from "@/lib/utils"; type AlertDialogProps = React.ComponentProps<typeof Primitive.Root>; function AlertDialog(props: AlertDialogProps) { return <Primitive.Root data-slot="alert-dialog" {...props} />; } type AlertDialogTriggerProps = React.ComponentProps<typeof Primitive.Trigger>; function AlertDialogTrigger({ className, ...props }: AlertDialogTriggerProps) { return ( <Primitive.Trigger data-slot="alert-dialog-trigger" className={className} {...props} /> ); } type AlertDialogContentProps = React.ComponentProps< typeof Primitive.Content > & { /** Width of the confirmation panel. */ size?: "default" | "sm"; }; function AlertDialogContent({ className, children, size = "default", ...props }: AlertDialogContentProps) { return ( <Primitive.Portal> <Primitive.Overlay data-slot="alert-dialog-overlay" className="fixed inset-0 z-50 bg-black/55 backdrop-blur-[2px] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 motion-reduce:animate-none" /> <Primitive.Content data-slot="alert-dialog-content" data-size={size} className={cn( "fixed top-1/2 left-1/2 z-50 grid max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-5 overflow-y-auto rounded-xl border bg-card p-5 text-card-foreground shadow-[0_24px_48px_-12px_rgb(0_0_0/0.25)] outline-none sm:p-6", "data-[size=default]:max-w-md data-[size=sm]:max-w-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 motion-reduce:animate-none", className, )} {...props} > {children} </Primitive.Content> </Primitive.Portal> ); } type AlertDialogHeaderProps = React.ComponentProps<"div">; function AlertDialogHeader({ className, ...props }: AlertDialogHeaderProps) { return ( <div data-slot="alert-dialog-header" className={cn("flex flex-col gap-2", className)} {...props} /> ); } type AlertDialogMediaProps = React.ComponentProps<"div">; function AlertDialogMedia({ className, ...props }: AlertDialogMediaProps) { return ( <div data-slot="alert-dialog-media" aria-hidden="true" className={cn( "flex size-11 items-center justify-center rounded-xl bg-destructive/10 text-destructive [&_svg]:size-5", className, )} {...props} /> ); } type AlertDialogTitleProps = React.ComponentProps<typeof Primitive.Title>; function AlertDialogTitle({ className, ...props }: AlertDialogTitleProps) { return ( <Primitive.Title data-slot="alert-dialog-title" className={cn("text-lg font-semibold tracking-tight", className)} {...props} /> ); } type AlertDialogDescriptionProps = React.ComponentProps< typeof Primitive.Description >; function AlertDialogDescription({ className, ...props }: AlertDialogDescriptionProps) { return ( <Primitive.Description data-slot="alert-dialog-description" className={cn("text-sm leading-relaxed text-muted-foreground", className)} {...props} /> ); } type AlertDialogFooterProps = React.ComponentProps<"div">; function AlertDialogFooter({ className, ...props }: AlertDialogFooterProps) { return ( <div data-slot="alert-dialog-footer" className={cn( "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className, )} {...props} /> ); } type AlertDialogCancelProps = React.ComponentProps<typeof Primitive.Cancel>; function AlertDialogCancel({ className, ...props }: AlertDialogCancelProps) { return ( <Primitive.Cancel data-slot="alert-dialog-cancel" className={cn( "inline-flex h-9 items-center justify-center rounded-md border bg-background px-4 text-sm font-medium outline-none transition-colors duration-150 hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50 motion-reduce:transition-none", className, )} {...props} /> ); } type AlertDialogActionProps = React.ComponentProps<typeof Primitive.Action> & { /** Highlight a destructive commitment with the semantic destructive token. */ destructive?: boolean; }; function AlertDialogAction({ className, destructive = false, ...props }: AlertDialogActionProps) { return ( <Primitive.Action data-slot="alert-dialog-action" data-destructive={destructive} className={cn( "inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground outline-none transition-colors duration-150 hover:bg-primary/90 focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50 data-[destructive=true]:bg-destructive data-[destructive=true]:text-white data-[destructive=true]:hover:bg-destructive/90 dark:data-[destructive=true]:bg-destructive/60 dark:data-[destructive=true]:hover:bg-destructive/50 motion-reduce:transition-none", className, )} {...props} /> ); } export { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogMedia, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, type AlertDialogProps, type AlertDialogTriggerProps, type AlertDialogContentProps, type AlertDialogHeaderProps, type AlertDialogMediaProps, type AlertDialogTitleProps, type AlertDialogDescriptionProps, type AlertDialogFooterProps, type AlertDialogCancelProps, type AlertDialogActionProps, };Update the import paths to match your project setup.
Usage
import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogMedia, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from "@/components/ballmac/alert-dialog"The full example is in the Code tab above.
Examples
Confirmation styles
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ballmac/alert-dialog";
export default function AlertDialogStates() {
return (
<div className="w-full max-w-xs">
<AlertDialog>
<AlertDialogTrigger className="h-9 rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50">
Publish changes
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogTitle>Publish this update?</AlertDialogTitle>
<AlertDialogDescription>
Your team will see the new version immediately.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Review again</AlertDialogCancel>
<AlertDialogAction>Publish now</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}API reference
<AlertDialogContent>
| Prop | Type | Default |
|---|---|---|
sizeWidth of the confirmation panel. | "default" | "sm" | "default" |
<AlertDialogAction>
| Prop | Type | Default |
|---|---|---|
destructiveHighlight a destructive commitment with the semantic destructive token. | boolean | false |
Also accepts the standard attributes of its root element.
Accessibility
| Key | Action |
|---|---|
| Tab / Shift+Tab | Moves within the modal choices |
| Enter / Space | Activates the focused choice |
| Escape | Cancels when the dialog permits dismissal |
Use with AI
Ask for explicit confirmation before a consequential action; cancel remains the safe keyboard path. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Alert Dialog (@ballmac/alert-dialog) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Delete or revoke something permanent
- Confirm an action with a material consequence
Not for
- Show information without requiring a choice; use dialog
- Confirm a low-risk routine action
Registry JSON: https://ui.ballmac.com/r/alert-dialog.json
Credits
Free to use in personal and commercial projects.
- npm
- radix-ui
- Registry
- shadcn/utils
Pairs well with
Button
A button with six variants, three sizes, a pill shape and a built-in loading state. buttonVariants() styles links the same way.
Alert
A semantic callout with five theme-aware tones, clear icon placement, an action row, and opt-in urgent announcements.
Banner
A page-wide announcement with semantic tones, optional action, and controlled or local dismissal.
Callout
An editorial guidance panel with a clear kind label, icon, title, and optional action.