‹ components

LoadingButton

draftv0.9.0·open in canvas

Async action button — width-stable idle → pending → success|error → idle. Interior loading-button contract, CIDS tokens, no `motion`.

Installnpx shadcn add @cids/loading-button
Live demo

Motion · tap to replay

honors reduced-motion

Label faces: `transition-opacity` 150ms (stacked grid — zero width jump) Press: `active:scale-[0.96]` when idle/settled (suppressed while pending) Spinner: `animate-spin` (global `prefers-reduced-motion` collapses the loop) No `motion` package — portable core

Brand chip remounts on tap · highlighted tokens are named in this component's doc · spring is budgeted to human feedback only.

import { LoadingButton } from "@/design-system";

<LoadingButton
  onAction={async () => {
    const res = await fetch("/api/orders", { method: "POST" });
    if (!res.ok) throw new Error("failed");
  }}
  pendingLabel="Signing…"
  successLabel="Signed"
  errorLabel="Retry"
>
  Sign transaction
</LoadingButton>

Tokens · live

Resolves in the active theme — flip the toggle above to verify.

States · from doc

Parsed from the doc. Interact with the live demo for hover/active.

**idle** — idle label; clickable**disabled** — native attribute40% opacity

Async action button — width-stable idle → pending → success|error → idle. Interior loading-button contract, CIDS tokens, no motion.

Anatomy

┌─────────────────────────────┐
│ [spinner|✓|!]  Label        │ ← faces stacked in one grid cell
└─────────────────────────────┘
  idle → pending → success|error → idle (resetAfter)
  width = max(face) — no layout jump

Props

PropTypeDefaultNotes
onAction() => unknown—Sync throw / rejected promise → error; else success.
childrenstring—Idle label (also the accessible name base).
pendingLabelstringchildrenIn-flight label.
successLabelstring"Done"Settled-ok label + check.
errorLabelstring"Try again"Settled-fail label + alert — next action, not diagnosis.
resetAfternumber1400ms to hold success/error before idle.
disabledbooleanfalseGenuinely unavailable (native disabled). Busy ≠ disabled.
onError(error: unknown) => void—Caller reports the error; button only shows state.
variantButtonVariant"primary"Same surface map as Button.
sizeButtonSize"md"Shared height scale.
classNamestring—cn-merged.

Motion

A11y

Self-contained: copy the folder into any Tailwind+React app, or use the install command above. Docs and this page render the same file on disk — they cannot drift.