Overlays & Feedback

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.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

100
1600
Active edited usageDefault props
<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.json

Or, 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

PropTypeDefaultDescription
openboolean—Controlled open state; omit to let the component manage it
defaultOpenbooleanfalseInitial open state in uncontrolled mode
onOpenChange(open: boolean) => void—Called when the open state changes
triggerReactNode—Trigger element; an element gets an onClick wired in, a string is wrapped in a default button; focus returns to it on close
titleReactNode—Dialog title (referenced by aria-labelledby)
descriptionReactNode—Description below the title (referenced by aria-describedby)
childrenReactNode—Custom content below the description, e.g. a confirmation input
cancelLabelstring"Cancel"Cancel button label
confirmLabelstring"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
holdDurationnumber1600How long to hold in hold mode (ms)
confirmDisabledbooleanfalseDisables the confirm button
hideCancelbooleanfalseHides the cancel button; plain only
durationnumber100Overlay and panel enter/exit duration (ms), also written to --wb-confirm-dialog-duration
classNamestring—Extra className for the dialog panel

The ConfirmDialogVariant and ConfirmDialogConfirmMode types are also exported.

How it works

  • Three variants: alert and destructive use role="alertdialog" and ignore overlay clicks, closing only via buttons or Escape; plain uses role="dialog" and cancels on an overlay click.
  • Initial focus on open: alert/destructive focus the cancel button so a stray Enter cannot trigger the dangerous action; plain focuses the first focusable element in the panel.
  • Scroll locking adds padding-right equal 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; onConfirm fires 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", with aria-labelledby/aria-describedby pointing at the title and description
  • Tab/Shift+Tab cycle within the panel, and background nodes are made inert so 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-busy during an async confirm; in hold mode it is linked via aria-describedby to 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