# Confirm morph

> A destructive button that morphs into an inline confirmation, a spinner, and a result with undo.

- Type: Component (actions)
- Access: Free, open source
- Page: https://uiarc.dev/components/confirm-morph
- Markdown: https://uiarc.dev/components/confirm-morph/markdown
- Registry item: https://uiarc.dev/r/confirm-morph.json
- Source file: `registry/components/confirm-morph/confirm-morph.tsx`
- Dependencies: motion, lucide-react
- Keywords: actions, new, react confirm button, inline confirmation, delete confirmation button, confirm delete without modal, undo button, two step button, destructive action button

## When to use

- Deleting a selection in a table or file list, right where the button sits.
- Revoking access, removing a member, or discarding a draft.
- Actions that should offer Undo in place after they finish.

## When not to use

- Use dialog when the consequence needs more than one line, or when typing a name to confirm is required.
- Use action-button for safe async actions that need no question.
- Use hold-to-confirm when an accidental tap must be nearly impossible.

## Installation

### CLI

Run one of these in a project set up with `shadcn init`:

```bash
npx shadcn@latest add @uiarc/confirm-morph
pnpm dlx shadcn@latest add @uiarc/confirm-morph
yarn dlx shadcn@latest add @uiarc/confirm-morph
bunx --bun shadcn@latest add @uiarc/confirm-morph
```

The `@uiarc` name needs `"registries": { "@uiarc": "https://uiarc.dev/r/{name}.json" }` in `components.json`. Without it, use the full URL:

```bash
npx shadcn@latest add https://uiarc.dev/r/confirm-morph.json
```

### Manual

1. Install the dependencies:

```bash
npm install motion lucide-react
```

2. Copy the source into your project. Main file: `registry/components/confirm-morph/confirm-morph.tsx`

   The source is in the registry item: https://uiarc.dev/r/confirm-morph.json

3. Arc imports use the `@/` alias for `registry/` and `lib/`. Keep the same folders or update the import paths.

## Usage

```tsx
import { Trash2 } from "lucide-react";
import { ConfirmMorph } from "@/registry/components/confirm-morph/confirm-morph";

export function DeleteSelection({ ids }: { ids: string[] }) {
  return (
    <ConfirmMorph
      label="Delete"
      icon={<Trash2 size={16} strokeWidth={1.75} />}
      prompt={`Delete ${ids.length} files?`}
      onConfirm={() => deleteFiles(ids)}
      onUndo={() => restoreFiles(ids)}
    />
  );
}
```

## API reference

### ConfirmMorph

A button for destructive or important actions that morphs into an inline question, then a spinner, then a result with Undo.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` (required) | `ReactNode` | – | The resting label, such as "Delete". |
| `icon` | `ReactNode` | – | A plain icon before the resting label. |
| `prompt` | `ReactNode` | – | The question while confirming, such as "Delete 3 files?". Defaults to the label with a question mark. |
| `confirmLabel` | `string` | `"Delete"` | Confirm button label. |
| `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
| `pendingLabel` | `string` | `"Deleting"` | Shown beside the spinner while onConfirm resolves. |
| `doneLabel` | `string` | `"Deleted"` | Result label. |
| `errorLabel` | `string` | `"Couldn’t finish"` | Error label. |
| `retryLabel` | `string` | `"Retry"` | Retry button label on the error face. |
| `undoLabel` | `string` | `"Undo"` | Undo button label. |
| `undoingLabel` | `string` | `"Restoring"` | Shown beside the spinner while onUndo resolves. |
| `tone` | `"danger" \| "neutral"` | `"danger"` | danger colours the resting label and confirm button red; neutral uses the foreground. |
| `onConfirm` | `() => void \| Promise<unknown>` | – | Runs on confirm. Return a promise to show the pending face; a rejection shows the error face with Retry. |
| `onUndo` | `() => void \| Promise<unknown>` | – | Adds Undo to the result. Return a promise to show a pending face while it runs. |
| `onCancel` | `() => void` | – | Called when the question is cancelled, including by Escape, outside press, or timeout. |
| `state` | `"idle" \| "confirming" \| "pending" \| "done" \| "error"` | – | Controlled state. Pair it with onStateChange. |
| `defaultState` | `"idle" \| "confirming" \| "pending" \| "done" \| "error"` | `"idle"` | Starting state when uncontrolled. |
| `onStateChange` | `(state: ConfirmMorphState) => void` | – | Called on every state change. |
| `confirmTimeout` | `number` | `6000` | Milliseconds before an unanswered question returns to rest. Hovering pauses it; 0 turns it off. |
| `resultTimeout` | `number` | `5000` | Milliseconds a result stays before returning to rest. Hovering pauses it; 0 turns it off. |
| `cancelOnOutsidePress` | `boolean` | `true` | A press outside the control cancels an open question. |
| `disabled` | `boolean` | `false` | Disables the resting button. |
| `className` | `string` | – | Class on the root. |
| `ref` | `Ref<HTMLDivElement>` | – | The root element, which also takes focus while the action is pending. |

## Keyboard interactions

| Keys | Action |
| --- | --- |
| Enter / Space | Presses the focused face button: start, Cancel, Confirm, Undo, or Retry. |
| Tab / Shift+Tab | Moves between Cancel and Confirm while asking. |
| Escape | Cancels the question, or dismisses a result back to rest. |

## Accessibility

- The question face is a group labelled by the prompt, and Cancel takes focus first so Enter never confirms by accident.
- Focus follows the morph: each new face focuses its main button, and the root holds focus while pending with aria-busy.
- Prompts, pending labels, results, and "Undo is available" are announced through a polite live region.
- Timeouts pause while the pointer rests on the control and while the tab is hidden.

## Motion

- The surface width springs to each face (a bouncier spring when growing, a critically damped one when shrinking), so nothing around it jumps.
- Faces slide in from the right going forward and from the left going back, with a soft blur; the done check draws itself.
- A thin bar drains along the bottom edge for the timeout.
- Reduced motion removes the width spring, slide, blur, and draw; faces crossfade and the spinner slows.

## Responsive behavior

- The control is as wide as its current face; leave room beside it for the question face, which is wider than the resting button.
- Hover styles apply only on hover-capable fine pointers; the control keeps the small control height, so give Cancel and Confirm some space from nearby targets on touch layouts.

## Performance

- Only the current face and the one leaving render; a ResizeObserver per face measures width for the spring.
- The timeout is a single linear motion value animation that pauses and resumes, not a timer per frame.

## Notes for AI

- Use for destructive or important actions that deserve a second press but not a modal: delete, revoke, discard.
- Return the real promise from onConfirm; rejections show the error face with Retry, which reruns the same handler.
- Offer onUndo when the action can be reversed; then consider a short confirmTimeout or skipping the question in your own flow.
- Use hold-to-confirm when a deliberate press and hold fits better, and dialog when the consequence needs a full explanation.

## Related

- [Hold to confirm](https://uiarc.dev/components/hold-to-confirm/markdown): Confirm a destructive action by holding, not tapping.
- [Action button](https://uiarc.dev/components/action-button/markdown): A compact button for frequent toolbar actions.
- [Action morph](https://uiarc.dev/components/action-morph/markdown): A floating button that morphs into a quick-action menu, then an inline form, then back with a check.
- [Dialog](https://uiarc.dev/components/dialog/markdown): A focused surface for decisions that need attention.
- [Toast](https://uiarc.dev/components/toast/markdown): Brief confirmation for a completed background action.

## Also in buttons

- [Button](https://uiarc.dev/components/button/markdown): A clear, responsive action with quiet secondary states.
- [Split button](https://uiarc.dev/components/split-button/markdown): A primary action with a menu of nearby alternatives.
- [Copy button](https://uiarc.dev/components/copy-button/markdown): Copy a value with immediate confirmation.

## Guidance for AI tools

Confirm morph: A destructive button that morphs into an inline confirmation, a spinner, and a result with undo. 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://uiarc.dev/llms.txt
