MultiStateButton
Multi-state animated button with a slot-machine transition between states. Ships in two layers: MultiStateButton<K> is the generic primitive (any state union, no defaults); TransactionButton is a tx-flavored preset with the 7-state lifecycle and sensible defaults baked in. Most of the sections below use the preset; the last one drops down to the generic primitive.
State explorer
Click a pill to set the button state. The button only invokes onClick in the ready state.
Lifecycle simulator
Programmatically cycles through a transaction flow at ~1 s per step.
Lifecycle simulator (fixed width)
Same simulator with className="w-64" applied — the slot-machine y-slide still plays, but the button width stays locked through the whole flow. Compare with the auto-width simulator above.
Fixed width
Pass a width via className (e.g. w-64) to lock the button width. Content still fades between states; no layout reflow.
Interactive non-ready states
Each state can opt-in to interactivity via disabled: false and an onClick. The "ready" state can also be soft-locked by setting disabled: true.
<TransactionButton
state="finalized"
states={{
finalized: {
content: <>↗ Finalized — click me</>,
disabled: false,
onClick: () => openExplorer(blockHash),
},
}}
/>Custom content
Per-state content is fully replaceable — pass plain text, label + icon, or any JSX.
Generic MultiStateButton
Drop down to the underlying MultiStateButton<K> when your domain isn't a chain transaction. State keys are arbitrary; you own every per-state config (no defaults baked in).
type GenericState = "idle" | "loading" | "success" | "error";
<MultiStateButton<GenericState>
state={state}
states={{
idle: { content: "Run job", disabled: false, onClick: ... },
loading: { content: <Spinner />, className: "..." },
success: { content: "Done", disabled: false, onClick: reset },
error: { content: "Retry", disabled: false, onClick: reset },
}}
/>Usage
import { TransactionButton } from "@/components/ui/TransactionButton";
const [state, setState] = useState<TransactionButtonState>("ready");
<TransactionButton
state={state}
onClick={() => mutate()}
states={{ ready: { content: "Submit transaction" } }}
/>