Skip to content
LuoForge/Tungsten
Components/ConfirmDialog·Experimental

ConfirmDialog

since v11.4.0

Destructive-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/tungsten
pnpm add @hey-mike/tungsten
import { 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 />.

PropTypeDefaultDescription
open*boolean
onOpenChange*(open: boolean) => void
title*stringRequired heading — the alertdialog's accessible name.
description*stringSupporting 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.
confirmLabelstringConfirmConfirm action label. @defaultValue 'Confirm'
cancelLabelstringCancelCancel action label. @defaultValue 'Cancel'
onConfirm*() => voidFired 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'
loadingbooleanfalseMarks 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

Source