ConfirmDialog
since v11.4.0Destructive-action confirmation gate — the delete/archive prompt the
TableToolbar's bulk-action bar feeds into.
Wraps @radix-ui/react-alert-dialog. Unlike Dialog, AlertDialog uses
role="alertdialog", defaults focus to the Cancel action (destructive-safe),
and has no click-outside dismissal — the user must pick a button. Radix's
Cancel/Action sub-components close the dialog themselves (→
onOpenChange(false)); the confirm onClick={onConfirm} runs before that
close, so it is NOT separately wired to close.
Reuses Dialog's modal-depth infra so a ConfirmDialog opened from inside
another modal escalates its scrim + content above the parent.
Install
Add the package and import the component.
pnpm add @hey-mike/tungstenpnpm add @hey-mike/tungstenimport { ConfirmDialog } from '@hey-mike/tungsten';import { ConfirmDialog } from '@hey-mike/tungsten';Preview
Same fixtures used by the visual-regression suite.
Usage
apps/docs/app/snapshots/confirm-dialog/page.tsx
'use client';
import { useState } from 'react';
import { ConfirmDialog } from '@hey-mike/tungsten';
export default function ConfirmDialogSnapshot() {
// Controlled open so the alertdialog renders for the snapshot; the no-op
// handlers keep it pinned open (Radix would otherwise close on action click).
const [open] = useState(true);
return (
<main
data-testid="snapshot-root"
className="bg-page text-ink-1 min-h-screen p-8"
>
<h1 className="text-ink-2 mb-6 font-mono text-sm uppercase tracking-widest">
ConfirmDialog
</h1>
<ConfirmDialog
open={open}
onOpenChange={() => undefined}
onConfirm={() => undefined}
title="Delete 3 users?"
description="This permanently deletes the selected records and cannot be undone."
confirmLabel="Delete"
tone="danger"
/>
</main>
);
}
'use client';
import { useState } from 'react';
import { ConfirmDialog } from '@hey-mike/tungsten';
export default function ConfirmDialogSnapshot() {
// Controlled open so the alertdialog renders for the snapshot; the no-op
// handlers keep it pinned open (Radix would otherwise close on action click).
const [open] = useState(true);
return (
<main
data-testid="snapshot-root"
className="bg-page text-ink-1 min-h-screen p-8"
>
<h1 className="text-ink-2 mb-6 font-mono text-sm uppercase tracking-widest">
ConfirmDialog
</h1>
<ConfirmDialog
open={open}
onOpenChange={() => undefined}
onConfirm={() => undefined}
title="Delete 3 users?"
description="This permanently deletes the selected records and cannot be undone."
confirmLabel="Delete"
tone="danger"
/>
</main>
);
}
Props
Surface specific to <ConfirmDialog />.
| Prop | Type | Default | Description |
|---|---|---|---|
| open* | boolean | — | — |
| onOpenChange* | (open: boolean) => void | — | — |
| title* | string | — | Required heading — the alertdialog's accessible name. |
| description* | string | — | Supporting text — the consequence being confirmed. Required: an
AlertDialog must describe the action for screen-reader users (Radix wires
aria-describedby to it), and a confirm gate should always state its
consequence. |
| confirmLabel | string | Confirm | Confirm action label. @defaultValue 'Confirm' |
| cancelLabel | string | Cancel | Cancel action label. @defaultValue 'Cancel' |
| onConfirm* | () => void | — | Fired when the confirm action is chosen. The consumer closes via onOpenChange. |
| tone | "default" | "danger" | default | 'danger' renders the confirm button in the destructive variant. @defaultValue 'default' |
| loading | boolean | false | Marks the confirm action as in-flight: the confirm button shows a spinner
and both buttons disable, blocking double-submit while an async onConfirm
runs. The dialog does NOT auto-close while loading — keep open true by
guarding onOpenChange (e.g. onOpenChange={(o) => { if (!loading) setOpen(o); }})
until the work resolves.
@defaultValue false |