<!-- Protocore Design System — Popconfirm -->
# Popconfirm

- **Category:** Overlay (`overlay`)
- **Slug:** `overlay/popconfirm`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { Popconfirm } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/overlay/popconfirm

> A small confirm bubble anchored to its trigger — a title plus confirm/cancel — for low-stakes actions that still deserve a second tap.

## When to use it

A Popconfirm is the **inline gut-check** for a reversible or low-consequence action — remove a filter, revoke a dev key, discard a draft — where a full modal would be too heavy but a bare click is too easy to trigger by accident. It keeps the user in context, right next to the button they pressed.

Escalate to an **[AlertDialog](/overlay/alert-dialog)** or **[ModalsProvider](/overlay/modals-provider)** `openConfirm` when the action is genuinely destructive and irreversible — a centered, backdropped prompt commands more attention than a bubble. Use a plain **[Popover](/overlay/popover)** when you need arbitrary anchored content rather than a yes/no decision.

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `align` | `enum` | no | `center` | Alignment along that side. |
| `cancelLabel` | `string` | no | `Cancel` | Label for the cancel action. |
| `children` | `ReactNode` | yes | — | The control that opens the bubble — rendered via `asChild`. |
| `className` | `string` | no | — | Extra class on the bubble panel. |
| `confirmLabel` | `string` | no | `Confirm` | Label for the confirm action. |
| `defaultOpen` | `boolean` | no | — | Uncontrolled initial open state. |
| `description` | `ReactNode` | no | — | Optional supporting copy explaining the consequence. |
| `onCancel` | `(() => void)` | no | — | Called when the user cancels (button or dismissal). |
| `onConfirm` | `(() => void)` | no | — | Called when the user confirms. |
| `onOpenChange` | `((open: boolean) => void)` | no | — | Fires when the open state changes. |
| `open` | `boolean` | no | — | Controlled open state. |
| `side` | `enum` | no | `top` | Side of the trigger to render on. |
| `title` | `ReactNode` | yes | — | Heading of the confirmation. |
| `tone` | `enum` | no | `danger` | Severity of the confirm action — danger renders the destructive button. |

## Examples

### Basics

Wrap the control that triggers the action. The bubble opens anchored to it with a `title`, an optional `description`, and confirm/cancel buttons. Confirming or cancelling closes the bubble and leaves the trigger in place.

```tsx
import { Popconfirm, Button } from "@protocore/pds";

export default function PopconfirmBasics() {
  return (
    <Popconfirm
      title="Revoke this API key?"
      description="Apps still using it will start receiving 401s immediately."
      confirmLabel="Revoke"
      tone="danger"
      onConfirm={() => {}}
    >
      <Button variant="danger">Revoke key</Button>
    </Popconfirm>
  );
}
```

### Placement

`side` (`top` default, `right`, `bottom`, `left`) and `align` position the bubble against the trigger; placement is collision-aware and flips to stay on screen.

```tsx
import { Popconfirm, Button, HStack } from "@protocore/pds";

export default function PopconfirmPlacement() {
  return (
    <HStack gap={3}>
      <Popconfirm title="Confirm on the right?" side="right" tone="primary" onConfirm={() => {}}>
        <Button variant="secondary">Right</Button>
      </Popconfirm>
      <Popconfirm title="Confirm below?" side="bottom" tone="primary" onConfirm={() => {}}>
        <Button variant="secondary">Bottom</Button>
      </Popconfirm>
      <Popconfirm title="Confirm above?" side="top" tone="primary" onConfirm={() => {}}>
        <Button variant="secondary">Top</Button>
      </Popconfirm>
    </HStack>
  );
}
```

## Do & don't

**Do**

- Use it for low-stakes, reversible actions next to their trigger.
- Set tone="danger" for removals so the confirm button reads as destructive.
- Keep the title to a single question; add one line of description if needed.
- Wrap the real trigger control as the child — it becomes the anchor.

**Don't**

- Use a bubble for irreversible, high-blast-radius actions — reach for AlertDialog.
- Cram a form into it; it's a yes/no decision, not a panel.
- Stack a Popconfirm on top of another overlay.
- Write a paragraph in the title — that's what description is for.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Enter / Space` | On the trigger, opens the bubble. |
| `Tab` | Moves between the cancel and confirm buttons. |
| `Esc` | Closes the bubble without confirming. |

**Notes**

- The bubble is role=alertdialog, labelled by its title and described by its description.
- Built on Radix Popover: focus moves into the bubble and returns to the trigger on close.
- Both actions are real buttons — keyboard-reachable and screen-reader friendly.

## Related

`popover`, `alert-dialog`, `modals-provider`, `dropdown-menu`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
