ActionButton
<ActionButton> is <Button> plus an in-button mutation state machine. Pass a mutation prop with { status, error }; the button shows a spinner while pending, ✓ on success (auto-restores), and surfaces the error inline / via tooltip on failure.
When to use
- Any button that triggers a fetch / mutation. Save, Publish, Delete (paired with Confirm).
- Submit buttons in forms —
mutationcomes from your hook (react-query, SWR, …).
Don’t use ActionButton for: navigation links (use <a> or <Button asChild>), pure UI toggles (Button is enough).
States
<ActionButton mutation={mutationLike}>Save</ActionButton>mutationLike is { status: 'idle' | 'pending' | 'success' | 'error', error: unknown } — works directly with react-query / SWR / TanStack Query mutation results.
Round trip
const [m, setM] = useState({ status: 'idle', error: null });
<ActionButton mutation={m} onClick={async () => { setM({ status: 'pending', error: null }); try { await api.save(); setM({ status: 'success', error: null }); } catch (e) { setM({ status: 'error', error: e }); } }}> Save</ActionButton>API
| Prop | Type | Default | Description |
|---|---|---|---|
mutation * | { status, error } | — | `status` is "idle" | "pending" | "success" | "error". `error` is the thrown value when status="error". |
idleIcon | ReactNode | — | Leading icon while idle. |
pendingText | ReactNode | — | Label swapped in while pending. Falls back to children. |
successText | ReactNode | — | Label swapped in while success. |
successTimeout | number | 1500 | Ms before success state restores to idle. 0 = sticky until next click. |
errorPlacement | "tooltip" | "below" | "below-absolute" | "none" | "tooltip" | Where the error message surfaces. |
formatError | (err: unknown) => string | — | Override the default error-message formatter (extractErrorMessage). |
onError | (err: unknown) => void | — | Fires once on transition into error. Useful for toast-lifting. |
tooltip | ReactNode | — | Hover tooltip while idle. Overridden by error message in tooltip placement. |
disabledReason | ReactNode | — | Tooltip shown only while disabled. |
...rest | ButtonProps | — | All Button props (variant, size, asChild, …). |
Design guidelines
✓ Do
- Pass the mutation result directly. Don't mirror state into your own component.
- Use errorPlacement="tooltip" for icon-only buttons; "below" inside forms next to FormError.
- Pair with onError to lift submission errors into a Sonner toast when the user has navigated away.
✗ Don't
- Set disabled while pending — ActionButton already handles this.
- Show success indefinitely (successTimeout=0) for routine saves. Stickiness reads as "this didn't work".
- Use ActionButton for non-async clicks. Plain Button is faster.
Accessibility
- Inherits
<Button>semantics — proper focus ring, keyboard activation. aria-busy="true"while pending; screen readers announce loading.- Error in tooltip mode pairs with
aria-describedbyreferring to the tooltip content.
Related
Button— Underlying primitive.MutationStatus— Same state machine, no button — for inline status next to other controls.Form— Pair the submit button with FormError above it.