Popconfirm

A confirmation anchored to the control that triggered it, for reversible or single-item actions — so the row you are asking about stays visible.

src/components/ui/popconfirm.tsx

About

From the component's own source, where it was written.

A confirmation anchored to the control that triggered it.

**The case against using AlertDialog for everything.** A modal steals focus, covers the page, and forces a full context switch — which is correct for "delete this project, and everything in it", and disproportionate for "remove this row". Worse, in a table it hides the very row you are asking about, so the answer to "which one was it?" is behind the thing asking. This keeps the row visible and puts the question next to the button.

**The rule for choosing between them is reversibility, not tone.** If the action can be undone, or affects one item, confirm in place. If it destroys something irrecoverable, or several things at once, take over the screen — the interruption is the feature.

Focus moves to the confirm button on open and returns to the trigger on close, Escape cancels, and it is role="alertdialog" so it is announced rather than silently appearing. It is **not** modal: the rest of the page stays reachable, which is the trade being made.

Better still, where you can: act immediately and offer an Undo toast. A confirmation people click through reflexively protects nobody.

Install

npx astralyx-ui add popconfirm

Usage

import { Popconfirm } from '@/components/ui/popconfirm'

<Popconfirm title="Remove this?" onConfirm={remove}>
  <Button>Remove</Button>
</Popconfirm>

Composer

Change the props and the source below follows.

  • staging-api
  • edge-cache
  • worker-eu
Props
tsx
<Popconfirm
  title="Remove this service?"
  destructive={true}
  side="top"
  onConfirm={remove}
>
  <Button>Remove</Button>
</Popconfirm>

Examples

Removing a row

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

Popconfirm

Extends Omit<ComponentProps<'div'>, 'title' | 'onSelect'>.

PropTypeDefaultDescription
childrenReactNoderequiredThe trigger. Cloned with a ref and the ARIA wiring, so it must accept one.
titleReactNoderequired—
descriptionReactNode——
onConfirm() => void——
onCancel() => void——
confirmLabelstring'Confirm'—
cancelLabelstring'Cancel'—
destructivebooleanfalseRed confirm button, for a destructive action.
loadingbooleanfalseSpinner on the confirm button while an async action runs.
side'top' | 'bottom' | 'left' | 'right''top'—
iconReactNode——
openboolean——
onOpenChange(open: boolean) => void——
disabledboolean——
inwhy it matters—A modal covers the very row you are asking about, so "which one was it?" is hidden behind the question.

Behaviour

TopicDetailDescription
vs AlertDialogreversibilityIf it can be undone, or affects one item, confirm in place. If it destroys something irrecoverable or several things at once, take over the screen — the interruption is the feature.
not modalthe traderole="alertdialog" so it is announced, focus moves to confirm and returns to the trigger, Escape cancels — but the rest of the page stays reachable.
better stillundo insteadWhere you can, act immediately and offer an Undo toast. A confirmation people click through reflexively protects nobody.