Confirm Dialog
A confirmation dialog that demands a response: the overlay fades in and the panel scales up, with focus and scroll locking, and destructive actions can require a press-and-hold fill to confirm.
A confirmation dialog for actions that need an explicit answer — delete, leave, overwrite. On open the overlay fades in and the panel scales up from 95% (100ms by default); focus is trapped inside the panel, the background is made inert and page scroll is locked, and focus returns to the trigger on close. variant sets the semantics and how it closes: alert closes only via its buttons or Escape, plain also closes on an overlay click, and destructive switches to a red primary button with a warning icon. For dangerous actions, set confirmMode to hold so the button must be held until it fills, preventing slips. When onConfirm returns a Promise the button shows a spinner and the dialog closes only on success, staying open on failure.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add confirm-dialog.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ConfirmDialog/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/confirm-dialog.en.jsonOr, once registries are configured in components.json, install it as @webberui/confirm-dialog.
Usage
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
<ConfirmDialog
trigger={<button type="button">Delete project</button>}
variant="destructive"
confirmMode="hold"
title="Delete this project?"
description="You can restore it from the archive within 30 days; after that it is removed permanently."
confirmLabel="Delete project"
onConfirm={async () => {
// Return a Promise: closes on success, stays open on failure
await deleteProject();
}}
/>You can also omit trigger and control it from outside with open/onOpenChange; on close, focus goes back to whatever was focused when it opened.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled open state; omit to let the component manage it |
defaultOpen | boolean | false | Initial open state in uncontrolled mode |
onOpenChange | (open: boolean) => void | — | Called when the open state changes |
trigger | ReactNode | — | Trigger element; an element gets an onClick wired in, a string is wrapped in a default button; focus returns to it on close |
title | ReactNode | — | Dialog title (referenced by aria-labelledby) |
description | ReactNode | — | Description below the title (referenced by aria-describedby) |
children | ReactNode | — | Custom content below the description, e.g. a confirmation input |
cancelLabel | string | "Cancel" | Cancel button label |
confirmLabel | string | "Confirm" | Confirm button label |
onConfirm | () => void | Promise<void> | — | Called on confirm; with a Promise it shows a spinner, closes on resolve and stays open on reject |
onCancel | () => void | — | Called on the cancel button, Escape, or (for plain) an overlay click |
variant | "plain" | "alert" | "destructive" | "alert" | Dialog semantics and appearance |
confirmMode | "click" | "hold" | "click" | With hold, the button must be held until it fills |
holdDuration | number | 1600 | How long to hold in hold mode (ms) |
confirmDisabled | boolean | false | Disables the confirm button |
hideCancel | boolean | false | Hides the cancel button; plain only |
duration | number | 100 | Overlay and panel enter/exit duration (ms), also written to --wb-confirm-dialog-duration |
className | string | — | Extra className for the dialog panel |
The ConfirmDialogVariant and ConfirmDialogConfirmMode types are also exported.
How it works
- Three variants:
alertanddestructiveuserole="alertdialog"and ignore overlay clicks, closing only via buttons or Escape;plainusesrole="dialog"and cancels on an overlay click. - Initial focus on open:
alert/destructivefocus the cancel button so a stray Enter cannot trigger the dangerous action;plainfocuses the first focusable element in the panel. - Scroll locking adds
padding-rightequal to the scrollbar width so the background layout does not jump when the scrollbar disappears; the original styles are restored on close. - Hold mode: pressing (mouse, touch, Space or Enter) sweeps a fill bar left to right at a constant speed; releasing or leaving rewinds it within 200ms; past 85% the button trembles slightly;
onConfirmfires only when it is full. - While an async confirm is running, the cancel button and Escape are temporarily disabled so the result never lands on a closed dialog.
- Timing variable: the buttons' color transitions read
--wb-confirm-dialog-duration, so you can override it at the theme level.
Accessibility
- The panel sets
aria-modal="true", witharia-labelledby/aria-describedbypointing at the title and description - Tab/Shift+Tab cycle within the panel, and background nodes are made
inertso neither screen readers nor the keyboard can reach them - Escape closes the dialog (during IME composition it only cancels the composition); on close, focus returns to the trigger or to the element focused before opening
- The confirm button sets
aria-busyduring an async confirm; in hold mode it is linked viaaria-describedbyto the "Hold to confirm" hint - With the system "reduce motion" setting on, the panel only fades in without scaling; the hold fill deepens in place instead and shows a text countdown ("Hold 2s… 1s…") with no trembling
Context Menu
A right-click menu that opens at the pointer, with delayed submenus, checkbox and radio items and shortcut hints, automatic edge flipping, and long-press support on touch.
Async State Slot
An async state slot that switches between the five idle/loading/empty/error/content states with height morphing and shared-element transitions.