Xzero UI
Pro library

Action swap

Swap an action label while preserving its place and intent.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui/action-swap
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui/action-swap

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { ActionSwap } from "@/components/xzero/action-swap/action-swap";
import { Send, Check } from "lucide-react";

export function SendInvite() {
  return (
    <ActionSwap
      states={[
        { id: "idle", label: "Send invite", icon: <Send size={16} /> },
        { id: "sent", label: "Sent", icon: <Check size={16} />, tone: "success", pendingLabel: "Sending", holdFor: 2000 },
      ]}
      onAction={next => (next.id === "sent" ? sendInvite() : undefined)}
    />
  );
}

Examples

Follow toggle

<ActionSwap
  states={[
    { id: "follow", label: "Follow", tone: "accent" },
    { id: "following", label: "Following", tone: "quiet", pendingLabel: "Following" },
  ]}
  onAction={next => setFollowing(next.id === "following")}
/>

API reference

ActionSwap

A compact pill that steps through a list of states on each press, morphing icon, tone, and letters together.

PropTypeDefaultDescription
states (required)ActionSwapState[]–Ordered states. Each press moves to the next and wraps around. Needs at least two to be enabled.
valuestring–Controlled state id.
defaultValuestring–Initial state id when uncontrolled. Defaults to the first state.
onValueChange(value: string, state: ActionSwapState) => void–Called when the control commits to a new state.
onAction(next: ActionSwapState, current: ActionSwapState) => void | PromiseLike<unknown>–Runs on press. Return a promise to show a working phase; a rejection shows failureLabel and stays put.
failureLabelstring"Try again"Label shown for 1.8s after onAction rejects or throws.
size"sm" | "md" | "lg""md"Height and padding.
...propsButtonHTMLAttributes<HTMLButtonElement>–Forwarded to the button, except children, onClick, onChange, and value.

ActionSwapState

One state in the states array.

PropTypeDefaultDescription
id (required)string–Stable id used by value and onValueChange.
label (required)string–Visible label and accessible name.
iconReactNode–Leading icon, sized at 16px.
tone"neutral" | "accent" | "success" | "warning" | "quiet""neutral"Fill color. Success draws its icon in with a stroke wipe.
pendingLabelstring–Shown while onAction runs on the way into this state. Without it the pill folds into a round spinner.
holdFornumber–Milliseconds to hold this state before advancing on its own, for brief confirmations.

Keyboard interactions

KeysAction
Enter / SpaceAdvances to the next state.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in buttons

Guidance for AI tools

Action swap: Swap an action label while preserving its place and intent. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components