# Action Button (/docs/action-button)

A button that confirms an asynchronous action with loading, success, and error states.

## Install

```bash
npx shadcn@latest add https://4af.selfsimilar.dev/r/action-button.json
```

## Usage

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

<ActionButton
  action={() => saveChanges()}
  labels={{ success: "Saved" }}
>
  Save changes
</ActionButton>
```

## API

**Main export:** `ActionButton`

Runs an async `action` and shows pending, success, and error states without changing width,
then reverts to idle.

`size` uses the Button scale: `xs` is 24px, `sm` is 30px, `md` is 34px, and `lg` is 38px. The
square icon sizes are `icon-xs`, `icon-sm`, `icon`, and `icon-lg`.

Without a resting `icon`, the label stays centred while the status glyphs reserve their space.
With an icon, the icon and label stay centred as a group.

`pendingMode` sets the pending presentation:

| `pendingMode` | Pending state |
| --- | --- |
| `"label"` (default) | The pending label beside the loader. |
| `"icon"` | The label morphs out and the loader morphs in, centred in the label-sized box. The button keeps its width, and the resting label stays its accessible name while `aria-busy` and `announce` describe the work. |

```tsx
<ActionButton
  action={() => saveChanges()}
  pendingMode="icon"
  labels={{ success: "Saved", error: "Try again" }}
  announce={{ pending: "Saving changes" }}
>
  Save changes
</ActionButton>
```

## Waiting states

`loader={<MySpinner />}` sets this button's indicator, and
[LoaderProvider](/docs/reference/engineering#shared-loading-indicators) sets a default for a
subtree. `AsciiLoader` is the fallback. Copy Button and Delete Button inherit the slot.

A label only animates when it changes. Pass `labels={{ pending: "Saving" }}` when the word should
change, and leave it out when only the glyph should move. With `pendingMode="icon"`,
`labels.pending` is not shown.