ConfirmDialog
Pre-configured dialog for confirming a single, often destructive action: a styleable, focus-trapped replacement for window.confirm().
Playground
<script lang="ts">
import { ConfirmDialog } from '@urbicon-ui/blocks';
let open = $state(false);
</script>
<ConfirmDialog
bind:open
cancelLabel="Cancel"
confirmLabel="Delete"
description="This cannot be undone."
intent="danger"
title="Delete project?"
/>01 Examples
Destructive confirmation
<script>
let confirmOpen = $state(false);
</script>
<Button intent="danger" onclick={() => (confirmOpen = true)}>Delete</Button>
<ConfirmDialog
bind:open={confirmOpen}
title="Delete project?"
description="This cannot be undone."
intent="danger"
confirmLabel="Delete"
onConfirm={() => deleteProject()}
/>Async onConfirm
<ConfirmDialog
bind:open
title="Submit report?"
description="Network call may take a moment."
intent="primary"
confirmLabel="Submit"
onConfirm={async () => { await api.submit(); }}
onError={() => toaster.danger('Could not submit report')}
/>02 Customization
Accented panel
<ConfirmDialog
bind:open
title="Publish release?"
description="Version 2.0 goes live for everyone."
intent="primary"
confirmLabel="Publish"
slotClasses={{ panel: 'bg-surface-elevated border-primary' }}
/>intent reaches both halves of the dialog, but not in
the same way: it tints the header title for every value except neutral, which leaves the header in the resting tone,
and it is what the confirm button wears — neutral included, so a neutral dialog gets a filled
neutral confirm button, because the accent belongs to the primary action rather than to every
confirmation. Put the accent back with confirmIntent="primary", which overrides the button
alone. For a richer body (a list of consequences, a typed-confirmation field) pass a children snippet, which renders below the description.
ConfirmDialog is a pre-configured Dialog and owns no styles, so class, unstyled, slotClasses and preset forward to it verbatim. Its slotClasses takes the Dialog slot keys, and a preset
registered under the Dialog key restyles both.
This is one of five ways to restyle a block. See Customization for class, slotClasses, unstyled, preset and provider-level overrides.
03 Accessibility
Inherited from Dialog
Focus trap, Escape-to-cancel and backdrop-click-to-cancel all come from the underlying Dialog. While an async onConfirm is pending, Escape and backdrop dismissal are
disabled and the close button is hidden, so the user cannot dismiss the dialog mid-action. The
focus trap stays active throughout.
Real buttons
Cancel and confirm are actual <button> elements, so keyboard navigation (Tab, Enter, Space) works without extra wiring.
Translatable labels
Default labels resolve via bt('button.confirm') / bt('button.cancel'). Translate them through the i18n package, or override per call via confirmLabel / cancelLabel.
04 API Reference
21 propsProp | Type | Default | Description | |
|---|---|---|---|---|
title required | string | — | Heading shown in the dialog header. | |
cancelLabel | string | — | Label of the cancel button. Defaults to the localized button.cancel. | |
children | Snippet | — | Optional richer markup rendered below description. | |
class | string | — | Extra classes merged onto the dialog **panel** — the card, not the
full-viewport <dialog> shell around it. Forwarded to
DialogProps.class, which lands on the same element. | |
closeOnBackdropClick | boolean | true | Whether the backdrop click cancels. | |
closeOnEscape | boolean | true | Whether Escape cancels. | |
confirmIntent | ConfirmIntent | — | Override for the confirm button alone, leaving the header strip on
ConfirmDialogProps.intent. Defaults to that intent; pass
'primary' to put the accent on a neutral dialog's confirm button. | |
confirmLabel | string | — | Label of the confirm button. Defaults to the localized button.confirm. | |
description | string | — | Description rendered above the footer. Use children for richer markup. | |
intent | DialogIntent | 'danger' | Intent of the dialog. It tints the header title and icon for every value
except neutral, which leaves them in the resting tone, and it is what the
confirm button wears by default — neutral reaches that button unchanged,
as a filled neutral confirm, because the accent belongs to the primary
action rather than to every confirmation. | |
loading | boolean | false | Externally controlled loading flag. Combined with the internal busy
flag from an async onConfirm. While truthy, the confirm button is busy
(spinner, still focusable, clicks dropped), the cancel button is
disabled, and dismissal is blocked — Escape, backdrop and the close
button. A close request that is not a key press (back gesture,
assistive tech) is vetoed twice; the browser honours the third without
user activation, and that arrives as an ordinary close. | |
onCancel | () => void | — | Fired when the user cancels (button, backdrop, or Escape). | |
onConfirm | () => void | Promise<void> | — | Confirm handler. May return a promise — the dialog stays open and shows
a loading state while it resolves, then auto-closes on success. If the
promise rejects the dialog stays open and re-enables; the rejection is
reported via ConfirmDialogProps.onError. | |
onError | (error: unknown) => void | — | Fired when an async onConfirm rejects (or a sync one throws). The
dialog stays open and re-enables so the user can retry or cancel — use
this to surface the failure (toast, inline message). Without a handler
the rejection is logged DEV-only (console.error) and swallowed in
production; it never escapes as an unhandled promise rejection. | |
open | boolean | — | Controls visibility. Supports bind:open. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ ConfirmDialog: {...} }}>.
Resolved against the **ConfirmDialog** key, not Dialog: a preset written
for the confirmation would otherwise style every dialog under the provider.
defaults.Dialog still applies — the resolved preset reaches Dialog as
instance slotClasses, so it wins over the provider's dialog-wide defaults
and loses to slotClasses / class written on this component.
A preset's overrides rules are matched against what you wrote here plus
Dialog's own variant defaults. Dialog's three axes (size, placement,
intent) are all received rather than derived, so no such mismatch exists
today — see #360 for the shape it takes where an axis is derived. | |
slotClasses | Partial<Record<DialogSlots, string>> | — | Per-slot class overrides, forwarded to the underlying Dialog. Slots: dialog | backdrop | panel | content | header | title | body | footer. | |
transitionDuration | number | — | Override the enter/exit animation duration in milliseconds, forwarded to
the underlying Dialog. Defaults to the overlay token
--blocks-overlay-enter-duration / --blocks-overlay-exit-duration
(200ms / 180ms). Respects prefers-reduced-motion. | |
transitionEasing | (t: number) => number | — | Override the enter/exit easing function, forwarded to the underlying
Dialog. Defaults to the overlay token easing (quintOut). | |
unstyled | boolean | false | Strip the underlying Dialog's default styles. Combine with slotClasses
for a fully custom appearance. | |
...HTMLDialogAttributes inherited | HTMLAttributes | — | HTML attributes (excluding: 'children' | 'open' | 'title' | 'draggable') |
05 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
ConfirmIntent | type | helper | 1 | Intent applied to the confirm button — the full DialogIntent palette,
neutral included. The accent belongs to the primary action, so a neutral
dialog's confirm button is a filled neutral one: the surface says "this is
the action", the words say what it does. | |
ConfirmDialogProps | interface | props | 0 | — | |
DialogIntent | type | helper | 1 | Semantic intent of the dialog. Tints the header markers (title, icon) and is
mirrored on the panel as data-intent. | |
DialogSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
DialogVariants | type | variant | 0 | — | |
SlotNames | type | helper | 0 | Extracts the slot-name union from a slotted tv() config function — the
companion to VariantProps. The slot-mode overload returns
(props?) => { [K in keyof S]: SlotFn }, so keyof ReturnType<T> is exactly
the set of slot names a component declares in tv({ slots: … }).
Use it to type a component's slotClasses prop from the single source of
truth (its *.variants.ts) instead of hand-maintaining a parallel union
that silently drifts when a slot is added or renamed: | |
VariantProps | type | helper | 0 | — |
06 Installation
Import
import { ConfirmDialog } from '@urbicon-ui/blocks';