VisorVisor
ComponentsAdmin

Confirm Dialog

Admin confirmation dialog compound wrapping Dialog with severity-driven icon and color, async-aware confirm handler, and an optional confirm-text gate for high-stakes destructive actions. Built with CSS Modules and container queries — copy it into your project and own it completely.

Severities

ConfirmDialog ships with three severity levels. Each severity swaps the leading icon, its color, and the confirm button variant.

Info

Warning

Danger

Confirm-Text Gate

For truly destructive operations — delete a production project, revoke a global API key — set confirmText to require the user to type an exact phrase before the confirm button is enabled. The match is case-sensitive.

Custom Body

Pass children for a richer body slot. Children override the description prop.

Installation

npx visor add confirm-dialog

This copies two files into your project and pulls in the dialog and button components as registry dependencies:

  • components/ui/confirm-dialog/confirm-dialog.tsx — the component
  • components/ui/confirm-dialog/confirm-dialog.module.css — the styles

Usage

import { ConfirmDialog } from '@/components/ui/confirm-dialog/confirm-dialog';
import { Button } from '@/components/ui/button/button';

export default function Example() {
  return (
    <ConfirmDialog
      trigger={<Button variant="destructive">Delete project</Button>}
      severity="danger"
      title="Delete project?"
      description="This action cannot be undone. All associated data will be permanently removed."
      onConfirm={async () => {
        await deleteProject();
      }}
    />
  );
}

API Reference

ConfirmDialogProps

PropTypeDefaultDescription
title*React.ReactNodeDialog title. Rendered next to the severity icon inside DialogTitle.
descriptionReact.ReactNodeShort body above the action row. Used as DialogDescription for aria-describedby.
childrenReact.ReactNodeRicher body slot. Overrides description when both are provided.
severity'info' | 'warning' | 'danger''warning'Drives icon, icon color, and confirm button variant. Danger uses the destructive button variant and focuses the cancel button on open.
triggerReact.ReactNodeOptional trigger wrapped in DialogTrigger. Omit for fully-controlled usage with `open`/`onOpenChange`.
openbooleanControlled open state.
defaultOpenbooleanUncontrolled initial open state.
onOpenChange(open: boolean) => voidCalled when the dialog open state changes.
confirmLabelReact.ReactNodeConfirm button label. Defaults to "Delete" when severity is "danger", otherwise "Confirm".
cancelLabelReact.ReactNode'Cancel'Cancel button label.
confirmTextstringIf set, the user must type this exact (case-sensitive) string to enable the confirm button.
confirmTextLabelReact.ReactNodeLabel for the confirm-text input. Defaults to "Type {confirmText} to confirm".
onConfirm() => void | Promise<void>Confirm handler. Returning a Promise enables pending state and keeps the dialog open until resolved. On rejection the dialog stays open and the error is re-thrown so the consumer can observe it.
onCancel() => voidCancel handler. Called before the dialog closes.
busybooleanExternally-controlled busy state. Overrides internal async pending detection and disables both buttons.
classNamestringAdditional CSS class names merged onto DialogContent.

Source Files

After running npx visor add confirm-dialog, you'll have:

confirm-dialog.tsx

A client component ("use client") that composes Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, and Button. The severity prop drives the leading Phosphor icon, its color, and the confirm button variant. For severity="danger", onOpenAutoFocus programmatically focuses the cancel button on open as a safer default.

If onConfirm returns a Promise, the component enters an internal pending state: both buttons are disabled, the confirm button gets aria-busy, and the dialog stays open until the promise settles. On rejection the dialog stays open and the error is re-thrown so the consumer's error boundary or onunhandledrejection can observe it. Consumers that need fully custom pending semantics can override everything with the busy prop.

confirm-dialog.module.css

All values use CSS custom properties from @loworbitstudio/visor-core. The action row is right-aligned on wide containers and stacks into a full-width column-reverse layout on narrow containers via a container query. The confirm-text input uses the standard focus ring tokens.

Customization

After copying the component, you own it completely. Common customizations:

  • Add a fourth severity level (e.g. critical) with its own icon and color mapping.
  • Swap the confirm-text comparison to be case-insensitive or fuzzy-trimmed.
  • Render a progress spinner inside the confirm button during pending state instead of only aria-busy.
  • Add a countdown timer that auto-disables the confirm button for N seconds on open.