Actions

Action Button

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

⠋

Install

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

Usage

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:

pendingModePending 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.
<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 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.

On this page