Confirm morph

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

npm install https://animui.stracture.net/__local/package.tgz
Live · keyboard ready
Northwind redesign
  • 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.
  • 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

Install animui once, then import any component or block from its typed package entry.

animui · 250 components and blocks

React 19 and React DOM 19 are required. The package includes Free and Pro entries, styles and TypeScript declarations.

npm install https://animui.stracture.net/__local/package.tgz

Load the stylesheet once at your app root, then use the typed component import:

import 'animui/styles.css';
import { ConfirmMorph } from 'animui/confirm-morph';

Read the Code tab for the implementation files and the API reference below for required props.

Download animui 0.3.0 · Agent guide
example.tsx
import { Trash2 } from "lucide-react";import { ConfirmMorph } from "animui/confirm-morph"; export function DeleteSelection({ ids }: { ids: string[] }) {  return (    <ConfirmMorph      label="Delete files"      icon={<Trash2 size={16} strokeWidth={1.75} />}      prompt={`Delete ${ids.length} files?`}      confirmMode="hold"      onConfirm={() => deleteFiles(ids)}      onUndo={() => restoreFiles(ids)}    />  );}

ConfirmMorph

A button for destructive or important actions that morphs in place into an inline question, then a spinner, then a result with Undo. Answer it with a click, a press and hold, or a click and a short countdown.

PropTypeDefaultDescription
labelRequiredReactNode–The resting label, such as "Delete project".
iconReactNode–A plain icon before the resting label.
promptReactNode–The question while confirming, such as "Delete 3 files?". Defaults to the label with a question mark.
confirmLabelstring"Delete"Confirm button label.
cancelLabelstring"Cancel"Cancel button label.
pendingLabelstring"Deleting"Shown beside the spinner while onConfirm resolves.
doneLabelstring"Deleted"Result label.
errorLabelstring"Couldn’t finish"Error label.
retryLabelstring"Retry"Retry button label on the error face.
undoLabelstring"Undo"Undo button label.
undoingLabelstring"Restoring"Shown beside the spinner while onUndo resolves.
tone"danger" | "neutral""danger"danger gives the confirm button the danger colour, for actions that destroy something; neutral gives it the accent, for important actions that can be reversed. The resting button is neutral either way.
confirmMode"press" | "hold" | "countdown""press"press confirms on a press. hold asks for the confirm button to be held for holdDuration while it fills. countdown confirms, then counts down countdown seconds that Cancel or Escape can still stop.
holdDurationnumber1000Milliseconds the confirm button must be held in hold mode.
countdownnumber3Seconds counted down before the action runs in countdown mode.
countdownLabelstring–Text before the seconds in countdown mode, such as "Deleting in". Defaults to pendingLabel followed by "in".
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.
confirmTimeoutnumber6000Milliseconds before an unanswered question returns to rest. Hovering pauses it; 0 turns it off.
resultTimeoutnumber5000Milliseconds a result stays before returning to rest. Hovering pauses it; 0 turns it off.
cancelOnOutsidePressbooleantrueA press outside the control cancels an open question.
disabledbooleanfalseDisables the resting button.
classNamestring–Class on the root.
refRef<HTMLDivElement>–The root element, which also takes focus while the action is pending.
EnterorSpace
Presses the focused face button: start, Confirm, Cancel, Undo, or Retry. In hold mode, holding Enter or Space on Confirm fills it.
Enter
While asking, confirms from the question (Confirm takes focus when it opens); during a countdown, runs the action now.
TaborShift+Tab
Moves between Cancel and Confirm while asking.
Escape
Cancels the question or the countdown, or dismisses a result back to rest.
  • The question face is a group labelled by the prompt. Confirm takes focus so Enter answers yes, but a press within 280ms of opening and auto-repeated keys are ignored, so a double click or a held Enter never confirms by accident.
  • Focus follows the morph: Confirm while asking, the root during a countdown or while pending (with aria-busy), Undo or Retry on a result, and the trigger back at rest.
  • In hold mode the confirm button is described as "Press and hold", and letting go early announces that it needs a longer hold.
  • 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.
  • Only the pill's width moves, on the morph spring growing and the smooth spring shrinking, so corners and text never scale or wobble and nothing around it jumps.
  • Every face sits at its natural width against the pill's leading edge: the question starts where the label was and the springing edge reveals the rest, so text never squashes, slides, or leaves its baseline.
  • The new face's parts arrive in reading order, each clearing a small blur; the old face fades out where it stood. The done check pops and draws itself, and countdown digits swap on tabular figures.
  • In hold mode a solid danger copy of the button is revealed from the left as the hold fills and drains back on a spring if released early.
  • Reduced motion removes the width spring, blur, and draw; faces crossfade and the spinner slows. The hold fill and countdown ring still show progress.
  • 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.
  • 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

Give your coding assistant the Markdown reference instead of screenshots.

  • 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 confirmMode="hold" for the riskiest deletes and "countdown" when there is no Undo; use dialog when the consequence needs a full explanation.
  • Keep the resting button neutral; tone="danger" only colours the confirm answer, and tone="neutral" moves it to the accent for reversible actions.

The full library index for assistants is at /llms.txt.