Marble UI
← Components

Popconfirm

Preview: Default

Lightweight inline confirmation: the trigger opens an alertdialog popover anchored to it. Use for reversible-ish actions; use AlertDialog (modal) for destructive ones that can't be undone.

Import

import { Popconfirm } from '@marbleui/ui'

Props

<Popconfirm>

PropTypeDefaultDescription
children *requiredReactElement<unknown, string | JSXElementConstructor<any>>—
onConfirm *required() => void | Promise<void>—
title *requiredReactNode—
cancelLabelstring'Cancel'
confirmLabelstring'Confirm'
descriptionReactNode—
destructiveboolean—
placement'bottom' | 'top''top'

Accessibility

Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria Dialog · React Aria DialogTrigger · React Aria Heading. Follow the primitive's docs for the full keyboard map.

Usage notes

Inline confirmation anchored to the button that triggered it: a small popover with a title, optional description, and Cancel/Confirm. For actions that can’t be undone use the modal AlertDialog.

<Popconfirm title="Archive this issue?" description="You can restore it from the archive." confirmLabel="Archive" onConfirm={() => archiveIssue(issue.id)}>
  <Button size="sm" variant="ghost">Archive</Button>
</Popconfirm>
  • The child must be one pressable element, such as Button or IconButton.
  • onConfirm may return a promise: the confirm button shows “Working…” and is disabled until it settles, then the popover closes. If it rejects, the popover stays open so the user can retry or cancel; the error is logged, so report it yourself, for example with a toast.
  • Focus starts on Cancel, so a stray Enter doesn’t confirm; Escape or clicking outside also cancels. The popover is a role="alertdialog" named by title.
  • destructive switches the confirm button and icon to danger colors. placement is top (default) or bottom.
Example source · show
import { useState } from 'react'
import { Button } from '../button'
import { Popconfirm } from './popconfirm'

function Demo() {
  const [items, setItems] = useState(['ACME-142 Fix login redirect', 'ACME-139 Audit color contrast', 'ACME-151 Billing empty state'])
  return (
    <ul className="w-96 divide-y divide-border rounded-xl border border-border bg-surface">
      {items.map((t) => (
        <li key={t} className="flex items-center justify-between gap-2 px-4 py-2 text-sm text-fg">{t}
          <Popconfirm title="Archive this issue?" description="You can restore it from the archive." confirmLabel="Archive" onConfirm={() => setItems((s) => s.filter((x) => x !== t))}><Button size="sm" variant="ghost">Archive</Button></Popconfirm>
        </li>
      ))}
    </ul>
  )
}
export const styles = {
  Default: Demo,
  Destructive: () => <Popconfirm destructive title="Revoke this API key?" description="Apps using it stop working immediately." confirmLabel="Revoke" onConfirm={() => new Promise((r) => setTimeout(r, 600))}><Button variant="outline">Revoke key</Button></Popconfirm>,
}

Source: packages/ui/src/popconfirm/popconfirm.tsx

Catalog

Category
overlays
Priority
Extra
Primitive
composed
React Aria
DialogTrigger + Popover
Base UI
Popover