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-dialogThis 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 componentcomponents/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
| Prop | Type | Default | Description |
|---|---|---|---|
title* | React.ReactNode | — | Dialog title. Rendered next to the severity icon inside DialogTitle. |
description | React.ReactNode | — | Short body above the action row. Used as DialogDescription for aria-describedby. |
children | React.ReactNode | — | Richer 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. |
trigger | React.ReactNode | — | Optional trigger wrapped in DialogTrigger. Omit for fully-controlled usage with `open`/`onOpenChange`. |
open | boolean | — | Controlled open state. |
defaultOpen | boolean | — | Uncontrolled initial open state. |
onOpenChange | (open: boolean) => void | — | Called when the dialog open state changes. |
confirmLabel | React.ReactNode | — | Confirm button label. Defaults to "Delete" when severity is "danger", otherwise "Confirm". |
cancelLabel | React.ReactNode | 'Cancel' | Cancel button label. |
confirmText | string | — | If set, the user must type this exact (case-sensitive) string to enable the confirm button. |
confirmTextLabel | React.ReactNode | — | Label 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 | () => void | — | Cancel handler. Called before the dialog closes. |
busy | boolean | — | Externally-controlled busy state. Overrides internal async pending detection and disables both buttons. |
className | string | — | Additional 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.
ChromeButton
A 28px-tall button primitive for topbar and chrome contexts with an optional leading icon and trailing Kbd shortcut hint. Built with CSS Modules — copy it into your project and own it completely.
Data Table
A generic Tanstack-powered data table compound that composes Visor's table primitive. Handles sorting, pagination, row selection, global filter, sticky header, loading, and empty states. Built with CSS Modules — copy it into your project and own it completely.