MultiStateButton
Generic multi-state animated button with a slot-machine / vertical-wheel label
transition between states. Domain-agnostic: you own the state union, each
state's content and styling, and which states are interactive. Honors
prefers-reduced-motion.
Installation
pnpm dlx shadcn@latest add peetzweg/ui/multi-state-buttonDepends on motion, lucide-react, and shadcn's button.
Usage
import { MultiStateButton } from "@/components/ui/multi-state-button"
type State = "idle" | "loading" | "done"
<MultiStateButton<State>
state={state}
states={{
idle: { content: "Run", disabled: false, onClick: run },
loading: { content: "Working…" },
done: { content: "Done", disabled: false, onClick: reset },
}}
/>The state union is a generic type parameter, so state and the keys of
states are checked against each other.
Presets
Opinionated, domain-specific presets built on this primitive — same mechanics, defaults baked in:
- TransactionButton — seven-state extrinsic-submission lifecycle with default content, palette, and form-submit semantics.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
state | TKey | — | The currently active state key. |
states | Record<TKey, MultiStateButtonStateConfig> | — | Per-state configuration (see below). |
className | string | — | Extra classes merged onto the button. |
type | "button" | "submit" | "reset" | "button" | Native button type. |
MultiStateButtonStateConfig
| Field | Type | Default | Description |
|---|---|---|---|
content | React.ReactNode | — | Node rendered inside the button for this state. |
className | string | — | Extra classes for this state only. |
disabled | boolean | true | Disabled by default — opt a state into being interactive with false. |
onClick | (e: React.MouseEvent<HTMLButtonElement>) => void | — | Click handler; receives the native event. |