Action button
ActionButton is Button + action-status for one-shot async work that stays on screen.
Idle glyph → spinner (min pending) → green check or red X → idle.
Use it for header / settings actions (sync, refresh, enqueue). Prefer raw
useActionStatus for disclosures and metric strips.
Prefer Button loading / Form.SubmitButton for form submit and dialogs that
close on success.
import { ActionButton } from "@vesyl/ui-next";
import { RefreshCcwIcon } from "lucide-react";
<ActionButton
variant="outline"
icon={<RefreshCcwIcon />}
action={async () => {
const ok = toastMutationResult({ /* errors only — no successMessage */ });
if (!ok) throw new Error("failed");
}}
>
Sync orders
</ActionButton>Contract: throw / reject on failure so the error face shows. Toast the error
message in the caller (or onError). Do not toast success — the check is the signal.
Labeled
Status face defaults to the start (leading). Pass iconPosition="end" for a trailing face.
Icon-only
Omit children; pass aria-label. Same lifecycle as labeled.
Error path
Failed action → red X (+ optional toast). The icon is ambient; the toast carries the message.
External triggers (ref)
When a menu or second control should drive the same face (e.g. multi-store sync):
const ref = useRef<ActionButtonHandle>(null);
<ActionButton ref={ref} icon={…} action={() => work(preferred)} />
// Dropdown item:
void ref.current?.run(() => work(otherStore));onStatusChange can lift pending to disable sibling controls.
vs other patterns
| Pattern | Use when |
|---|---|
| ActionButton | One-shot command; button remains; ambient ✓/✕ |
useActionStatus alone | Disclosure chevrons, multi-cell layouts, custom chrome |
Button loading / Form.SubmitButton | Form submit; dialog primary that closes/navigates on success |
Reference
ActionButton
Accepts all Button props except loading and onClick (click is owned by action).
| Prop | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | — | Idle face (required) |
action | () => Promise<unknown> | — | Default click work; throw → error |
iconPosition | 'start' | 'end' | 'start' | Status face when labeled |
successMs / errorMs / minPendingMs | number | same as action-status | Timing |
icons / labels | … | — | Status icon / live-region overrides |
onError | (error) => void | — | After catch (e.g. report); does not rethrow |
onStatusChange | (status) => void | — | Every status transition |
ActionButtonHandle (ref)
| Field | Description |
|---|---|
run(fn) | Same as useActionStatus().run |
isBusy | status === 'pending' |
status | Current ActionStatus |