Skip to content
peetzweg/ui

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-button

Depends 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

PropTypeDefaultDescription
stateTKeyThe currently active state key.
statesRecord<TKey, MultiStateButtonStateConfig>Per-state configuration (see below).
classNamestringExtra classes merged onto the button.
type"button" | "submit" | "reset""button"Native button type.

MultiStateButtonStateConfig

FieldTypeDefaultDescription
contentReact.ReactNodeNode rendered inside the button for this state.
classNamestringExtra classes for this state only.
disabledbooleantrueDisabled by default — opt a state into being interactive with false.
onClick(e: React.MouseEvent<HTMLButtonElement>) => voidClick handler; receives the native event.