Xzero UI
Pro library

Confirm popoverPro

A destructive button that grows into a small confirmation card, with the consequence, optional type-to-confirm, progress on the Confirm button, and a check or error before folding back.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/confirm-popover
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/confirm-popover

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { Trash2 } from "lucide-react";
import { ConfirmPopover } from "@/components/xzero/confirm-popover/confirm-popover";

export function DeleteProject({ project }: { project: Project }) {
  return (
    <ConfirmPopover
      icon={<Trash2 size={16} />}
      align="end"
      title={`Delete ${project.name}?`}
      description={`This permanently deletes ${project.deployments} deployments and every log. It can't be undone.`}
      confirmText={project.slug}
      confirmLabel="Delete project"
      errorTitle={`Couldn't delete ${project.name}`}
      onConfirm={async ({ setProgress, signal }) => {
        for await (const step of api.deleteProject(project.id, { signal })) setProgress(step.done / step.total);
      }}
      onDone={() => router.push("/projects")}
    >
      Delete
    </ConfirmPopover>
  );
}

API reference

ConfirmPopover

A destructive button that asks in place. Pressing it grows the button itself, as one surface, into a small floating card that states the consequence, with Cancel and Confirm and an optional phrase to type first. Confirm is also the progress bar: it widens as Cancel folds away and fills with the action's progress, its label inverting exactly at the fill edge. On success the card folds back into the button's shape with a check; on failure it stays open, shakes once and shows the error with Try again. Escape and a press outside cancel; focus moves into the card and back to the button.

PropTypeDefaultDescription
children (required)ReactNode–The trigger label, such as Delete project.
title (required)ReactNode–The question, such as Delete Atlas?. It names the dialog.
descriptionReactNode–The consequence in one or two short sentences. It describes the dialog.
iconReactNode–A plain 16px icon before the trigger label.
confirmTextstring–A phrase to type before Confirm unlocks, such as the project name. Matching ignores case and outer spaces. Confirm fills in the tone color as the typed prefix matches, and a wrong submit shakes the card.
confirmLabelstring"Delete"Label of the confirm button.
cancelLabelstring"Cancel"Label of the cancel button.
pendingLabelstring"Deleting"Label on the progress bar while onConfirm runs, followed by the percentage when progress is reported.
successLabelstring"Deleted"Shown with a check in the button's shape once onConfirm resolves. Keep it no longer than the trigger label so the width holds.
errorTitleReactNode"That didn't work"Heading of the failure. The rejection's message is shown under it.
retryLabelstring"Try again"Label of the confirm button after a failure.
tone"danger" | "neutral""danger"danger tints the trigger and fills Confirm in the danger color. neutral uses the foreground, for serious but reversible actions.
onConfirm(ctx: ConfirmPopoverContext) => void | Promise<unknown>–Runs the action. ctx.setProgress(0 to 1) drives the bar; without it the bar sweeps. ctx.signal aborts if the component unmounts. A rejection shows the failure.
onCancel() => void–Called when the question is dismissed by Cancel, Escape or a press outside.
onDone() => void–Called once the success state has been shown and the button is back at rest.
onError(error: unknown) => void–Called with the rejection when onConfirm fails.
openboolean–Controlled open state of the question. Pair with onOpenChange.
defaultOpenbooleanfalseOpens on mount when uncontrolled.
onOpenChange(open: boolean) => void–Called when the question opens, is dismissed, or closes after success.
onPhaseChange(phase: ConfirmPopoverPhase) => void–Called with idle, confirming, working, success or error.
align"start" | "center" | "end""start"Which edge of the button the card keeps. end grows toward the start, for buttons at the end of a row. The card shifts to stay 16px inside the window either way.
side"bottom" | "top" | "auto""auto"Grow downward, upward, or upward only when there is not room below.
widthnumber320Width of the open card in px, never wider than the window less 32px.
resultDurationnumber1600Milliseconds the success state holds before the button returns to rest.
disabledboolean–Disables the trigger.
classNamestring–Class on the root element.
styleCSSProperties–Inline styles on the root element.

Keyboard interactions

KeysAction
Enter / SpaceOn the button, opens the question. Focus moves to the phrase field when there is one, otherwise to Cancel.
Tab / Shift+TabCycles through the field, Cancel and Confirm while the card is open.
EnterIn the phrase field, confirms once the phrase matches; otherwise the card shakes and focus stays in the field.
EscapeCancels the question or dismisses a failure, and returns focus to the button once the card has folded back.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in overlays

Guidance for AI tools

Confirm popover: A destructive button that grows into a small confirmation card, with the consequence, optional type-to-confirm, progress on the Confirm button, and a check or error before folding back. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components