# Action button

> Asynchronous button with promise-driven state management, in-place character glyph morphing, self-drawing checkmark, and error shake physics.

A stateful execution button that manages the full asynchronous lifecycle: idle, pending, success, and error. Instead of jarring layout jumps or disconnected spinners, the icon slot opens organically, text morphs smoothly through TextMorph, and a drawn SVG checkmark confirms completion before gracefully reverting.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/action-button.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: none
- Hairline UI components it needs (the CLI installs them too): `text-morph`, `ui-interaction-motion`
- Docs and live preview: https://hairlineui.com/ui/action-button
- Registry item (source): https://hairlineui.com/r/action-button.json

## Usage

```tsx
import { ActionButton } from "@/components/ui/action-button";
import { CloudUpload } from "lucide-react";

<ActionButton
  icon={<CloudUpload size={14} />}
  labels={{
    idle: "Save changes",
    pending: "Saving...",
    success: "Saved!",
    error: "Failed",
  }}
  action={async () => {
    await saveSettings();
  }}
/>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `action` | `() => Promise<unknown> \| void` | – | Async or sync callback executed on click. Drives status automatically. |
| `status` | `"idle" \| "pending" \| "success" \| "error"` | – | Controlled status override. |
| `labels` | `Partial<Record<ActionStatus, string>>` | – | Custom labels per status state. |
| `variant` | `"primary" \| "secondary" \| "destructive" \| "outline" \| "ghost"` | `"primary"` | Visual style. |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Button scale and typography. |
| `resetAfter` | `number` | `2000` | Duration in ms before auto-reverting from success/error to idle. |

## States

- **idle**: Resting state with optional leading icon and label.
- **pending**: Interaction disabled; spinner opens and label morphs to pending state.
- **success**: Self-drawing checkmark and affirmative feedback before auto-resetting.
- **error**: Error badge and horizontal WAAPI shake alerting the user.

## What it handles

Async buttons often trigger awkward layout shifts, freeze UI interactivity, or rely on disconnected alert dialogs. This component handles async states in place with continuous kinetic transitions and zero runtime bloat.

- **Promise-driven state machine**: Pass an async action callback; the button manages idle, pending, success, and error states automatically.
- **In-place character morphing**: Labels transition using TextMorph, animating shared glyphs with spring physics.
- **Self-drawing checkmark**: On success, a crisp hairline checkmark draws itself with SVG strokeDashoffset easing.
- **Tactile error shake**: On failure, the button executes an organic horizontal shake via WAAPI to catch peripheral attention.

## Replaces

loading buttons with layout jumps, manual useState spinner logic, unresponsive submit triggers

## Where it fits

- **Form submissions**: Primary save/update actions that communicate network progress without modal overlays.
- **Deployments & builds**: Triggering long-running workflows with clear pending feedback and auto-reset.
- **Destructive confirmations**: Critical actions like purging data or deleting workspaces with affirmative feedback.

## Notes

- Uses WAAPI for zero-bundle error shake and native SVG stroke animation.
- Aria-live polite announcement keeps screen readers updated on status changes.

License: MIT (https://hairlineui.com/license#components)
