Unified UI

Alert Dialog

A modal dialog that requires explicit user action before proceeding. Cannot be dismissed by clicking the overlay. Built on Radix UI with Framer Motion animations.

Basic

A blocking modal that requires the user to make an explicit choice before continuing. Unlike Dialog, clicking the overlay does not dismiss it — the user must press Cancel or confirm the action.

Installation

Install the component via the CLI in one command.

npx @work-rjkashyap/unified-ui add alert-dialog
pnpm dlx @work-rjkashyap/unified-ui add alert-dialog
npx @work-rjkashyap/unified-ui add alert-dialog
bunx @work-rjkashyap/unified-ui add alert-dialog

If you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.

Or install the full package

Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.

npm install @work-rjkashyap/unified-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

import {
	AlertDialog,
	AlertDialogTrigger,
	AlertDialogContent,
	AlertDialogHeader,
	AlertDialogFooter,
	AlertDialogTitle,
	AlertDialogDescription,
	AlertDialogAction,
	AlertDialogCancel,
	Button,
} from "@work-rjkashyap/unified-ui";

Basic Usage

Wrap a trigger in AlertDialogTrigger and provide the content structure. The user must press either AlertDialogAction or AlertDialogCancel — clicking the overlay has no effect.

<AlertDialog>
	<AlertDialogTrigger asChild>
		<Button variant="danger">Delete</Button>
	</AlertDialogTrigger>
	<AlertDialogContent>
		<AlertDialogHeader>
			<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
			<AlertDialogDescription>
				This action is permanent and cannot be reversed.
			</AlertDialogDescription>
		</AlertDialogHeader>
		<AlertDialogFooter>
			<AlertDialogCancel>Cancel</AlertDialogCancel>
			<AlertDialogAction>Continue</AlertDialogAction>
		</AlertDialogFooter>
	</AlertDialogContent>
</AlertDialog>

Destructive Variant

Style the action button as a danger action for destructive operations.

Controlled Mode

Use open and onOpenChange for programmatic control — useful for triggering the dialog from custom logic without a visible trigger element.

const [open, setOpen] = useState(false);

function handleDeleteClick() {
	// Any logic before showing confirmation
	setOpen(true);
}

async function handleConfirm() {
	await deleteItem(item.id);
	setOpen(false);
}

<AlertDialog open={open} onOpenChange={setOpen}>
	<AlertDialogContent>
		<AlertDialogHeader>
			<AlertDialogTitle>Delete "{item.name}"?</AlertDialogTitle>
			<AlertDialogDescription>
				This item will be permanently deleted from your workspace.
			</AlertDialogDescription>
		</AlertDialogHeader>
		<AlertDialogFooter>
			<AlertDialogCancel onClick={() => setOpen(false)}>
				Cancel
			</AlertDialogCancel>
			<AlertDialogAction onClick={handleConfirm}>
				Delete
			</AlertDialogAction>
		</AlertDialogFooter>
	</AlertDialogContent>
</AlertDialog>;

With Loading State

Combine with a loading flag to disable actions while an async operation completes.

function DeleteDialog({ onDelete }: { onDelete: () => Promise<void> }) {
	const [loading, setLoading] = useState(false);
	const [open, setOpen] = useState(false);

	const handleConfirm = async () => {
		setLoading(true);
		try {
			await onDelete();
			setOpen(false);
		} finally {
			setLoading(false);
		}
	};

	return (
		<AlertDialog open={open} onOpenChange={setOpen}>
			<AlertDialogTrigger asChild>
				<Button variant="danger">Delete</Button>
			</AlertDialogTrigger>
			<AlertDialogContent>
				<AlertDialogHeader>
					<AlertDialogTitle>Confirm deletion</AlertDialogTitle>
					<AlertDialogDescription>
						This cannot be undone. Are you sure you want to proceed?
					</AlertDialogDescription>
				</AlertDialogHeader>
				<AlertDialogFooter>
					<AlertDialogCancel disabled={loading}>
						Cancel
					</AlertDialogCancel>
					<AlertDialogAction
						onClick={(e) => {
							e.preventDefault(); // Prevent auto-close
							handleConfirm();
						}}
						disabled={loading}
						className="bg-danger text-danger-foreground hover:bg-danger/90"
					>
						{loading ? "Deleting..." : "Delete"}
					</AlertDialogAction>
				</AlertDialogFooter>
			</AlertDialogContent>
		</AlertDialog>
	);
}

Non-Destructive Confirmation

Alert dialogs are not only for destructive actions — use them for any critical operation that benefits from explicit confirmation.

AlertDialog vs Dialog

FeatureAlertDialogDialog
Dismiss on overlay click✗ No✓ Yes
Dismiss on Escape✓ Yes✓ Yes
Requires explicit action✓ Always✗ Optional
Use for destructive actions✓ Recommended✗ Not recommended
AlertDialogAction / Cancel✓ Built-in✗ Custom
ARIA rolealertdialogdialog

Use AlertDialog when the user must explicitly confirm or cancel — never when they might want to simply dismiss without acting.

Props

AlertDialog

PropTypeDefaultDescription
openbooleanControlled open state.
defaultOpenbooleanInitial open state (uncontrolled).
onOpenChange(open: boolean) => voidCallback when the open state changes.

AlertDialogContent

PropTypeDefaultDescription
classNamestringAdditional CSS classes.

AlertDialogTitle

PropTypeDefaultDescription
classNamestringAdditional CSS classes.

AlertDialogDescription

PropTypeDefaultDescription
classNamestringAdditional CSS classes.

AlertDialogAction

Extends the native Radix UI AlertDialogAction — auto-closes the dialog when clicked. Pass onClick={(e) => e.preventDefault()} to prevent auto-close (e.g., for async flows).

PropTypeDefaultDescription
classNamestringOverride button styles (e.g., for danger color).
disabledbooleanfalseDisables the action button.

AlertDialogCancel

Auto-closes the dialog when clicked. Styled as a secondary button.

PropTypeDefaultDescription
classNamestringAdditional CSS classes.
disabledbooleanfalseDisables the cancel button.

AlertDialogTrigger

Uses Radix UI's AlertDialogTrigger API. Use asChild to render the trigger as your own element.

AlertDialogHeader / AlertDialogFooter

Layout wrappers with no interactive behavior.

PropTypeDefaultDescription
classNamestringAdditional CSS classes.

Motion

  • Overlay — Uses the overlayBackdrop preset (opacity: 0 → 0.5, backdrop-blur 0 → 4px, 200ms).
  • Content — Uses the modalContent preset (scale: 0.95 → 1, opacity: 0 → 1, spring stiffness: 300, damping: 28).
  • All animations respect prefers-reduced-motion via useReducedMotion() — falls back to simple opacity transitions.

Keyboard Interaction

KeyBehavior
EscapeCloses the dialog (same as pressing Cancel).
TabCycles focus between Cancel and Action buttons (and any focusable content).
EnterActivates the focused button.
SpaceActivates the focused button.

Accessibility

  • Built on @radix-ui/react-alert-dialog for full ARIA compliance.
  • The content element has role="alertdialog" — screen readers announce it as an alert, prompting immediate attention.
  • Focus trap — Focus is contained within the dialog. The Cancel button receives initial focus by default (to prevent accidental confirmation).
  • Focus restoration — Focus returns to the trigger element when the dialog closes.
  • Escape to close — Pressing Escape closes the dialog (treated as cancellation).
  • No overlay dismiss — Clicking the backdrop does nothing, enforcing explicit choice.
  • aria-labelledby — Automatically linked to AlertDialogTitle.
  • aria-describedby — Automatically linked to AlertDialogDescription.
  • aria-modal="true" — Prevents screen readers from interacting with content behind the dialog.

Design Tokens

TokenUsage
--backgroundDialog panel background
--borderDialog panel border
--overlayBackdrop overlay color
--radius-lgDialog border radius
--z-modalZ-index for dialog content
--z-overlayZ-index for backdrop
--shadow-xlDialog panel shadow
--primaryDefault action button color
--dangerDestructive action button color

On this page