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.tsxAbout
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
<Popconfirm
title="Remove this service?"
destructive={true}
side="top"
onConfirm={remove}
>
<Button>Remove</Button>
</Popconfirm>Examples
Removing a row
<Popconfirm title="Remove this service?" onConfirm={remove}><Button /></Popconfirm>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'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | required | The trigger. Cloned with a ref and the ARIA wiring, so it must accept one. |
| title | ReactNode | required | — |
| description | ReactNode | — | — |
| onConfirm | () => void | — | — |
| onCancel | () => void | — | — |
| confirmLabel | string | 'Confirm' | — |
| cancelLabel | string | 'Cancel' | — |
| destructive | boolean | false | Red confirm button, for a destructive action. |
| loading | boolean | false | Spinner on the confirm button while an async action runs. |
| side | 'top' | 'bottom' | 'left' | 'right' | 'top' | — |
| icon | ReactNode | — | — |
| open | boolean | — | — |
| onOpenChange | (open: boolean) => void | — | — |
| disabled | boolean | — | — |
| in | why it matters | — | A modal covers the very row you are asking about, so "which one was it?" is hidden behind the question. |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| vs AlertDialog | reversibility | If 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 modal | the trade | role="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 still | undo instead | Where you can, act immediately and offer an Undo toast. A confirmation people click through reflexively protects nobody. |