Confirm dialog
components/confirm-dialog.tsx: the desktop's destructive confirmation on the headless alert dialog
import { ConfirmDialog } from "../components/confirm-dialog.tsx";File: packages/desktop/src/renderer/src/components/confirm-dialog.tsx, over
@nyte-ai/ui/alert-dialog. Used for: delete session, archive all
chats in a workspace, discard unsaved file, close a running terminal, sign out of GitHub CLI.
Anatomy
AlertDialog.Root (open, onOpenChange)
└─ Portal
├─ Backdrop layer.dialogBackdrop, t.bgScrim
└─ Popup layer.dialog, min(400px, 100vw − 48px), initialFocus=cancel, finalFocus=returnFocusRef, aria-busy
├─ Title
├─ Description
├─ <p role="alert"> error, when present
└─ actions
├─ AlertDialog.Close (Button secondary) "Cancel"
└─ Button danger confirmLabel | pendingLabelProps
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | Required, controlled. | |
pending | boolean | Required. Disables both buttons, sets aria-busy, blocks closing. | |
error | string | undefined | Required. Rendered as role="alert" when defined. | |
returnFocusRef | RefObject<HTMLButtonElement | null> | Required. The control that opened the dialog; focus returns to it on close. | |
title | string | "Delete chat?" | |
description | ReactNode | "This permanently deletes the chat and its history. This can't be undone." | |
confirmLabel | string | "Delete" | |
pendingLabel | string | "Deleting…" | |
onOpenChange | (open: boolean) => void | Required. Only called with false, and never while pending. | |
onConfirm | () => void | Required. The dialog does not close itself; the caller closes after success. |
Behavior that must be kept
- Initial focus is on Cancel, not on the destructive action. Pressing Enter reflexively cancels.
finalFocus={returnFocusRef}is required so the row or button that opened the dialog gets focus back. Callers pass a ref to the exact element.- While
pending,onOpenChangeis swallowed: Escape and outside press do nothing until the mutation settles. - The error stays inside the dialog as a live
role="alert"; the dialog stays open so the user can retry or cancel.
Example
const deleteRef = useRef<HTMLButtonElement>(null);
const remove = useMutation(…);
<Button ref={deleteRef} variant="danger" onClick={() => setOpen(true)}>Delete</Button>
<ConfirmDialog
open={open}
pending={remove.isPending}
error={remove.error?.message}
returnFocusRef={deleteRef}
onOpenChange={setOpen}
onConfirm={() => remove.mutate(id, { onSuccess: () => setOpen(false) })}
/>Compared with @nyte-ai/ui AlertDialog
The shared Alert dialog is parts you compose; this is a finished surface with a fixed footer and a pending state. They share Base UI behavior and nothing else.