Button
Five variants, four sizes, spring press feedback, an animated loading spinner and in-place success state, a two-step confirm that swaps the label on first click, and a configurable swap direction. Width stays fixed to the longest label.
npx @kznlabs/labsui add buttonimport { Button } from "@/components/ui/button";export function SaveButton({ saving, saved,}: { saving: boolean; saved: boolean;}) { return ( <Button variant="outline" status={saving ? "loading" : saved ? "success" : "idle"} loadingLabel="Saving…" successLabel="Saved!" > Save </Button> );}import { Button } from "@/components/ui/button";export function DeleteAccount({ status, onDelete,}: { status: "idle" | "loading" | "success"; onDelete: () => Promise<void>;}) { return ( <Button variant="destructive" confirmLabel="Yes, delete my account" loadingLabel="Deleting…" successLabel="Account deleted" status={status} onClick={onDelete} > Delete account </Button> );}<Button>
childrenReactNodeLabel; its width is reserved so loading and success never resize the button.
confirmLabelReactNodeTwo-step action: first click swaps the label to this, second click runs
onClick.confirmTimeoutdefault3000numberMs before an armed confirm step resets. Also resets on blur or Esc.
loadingdefaultfalsebooleanDisables the button and shows a spinner before the label.
loadingLabelReactNodeLabel shown while loading, with the spinner. Defaults to
children.size"default" | "sm" | "lg" | "icon"Height and padding; "icon" is square, so give it an
aria-label.statusdefault"idle"ButtonStatusIn-place feedback:
loadingspins,successswaps the label tosuccessLabel.successLabelReactNodeText shown on
status="success", with a check icon. Defaults to "Done".swapdefault"left"SwapDirectionWhere the old label slides out to on a swap. Default "left".
variant"default" | "secondary" | "outline" | "ghost" | "destructive"Fill and emphasis: one primary action per view,
destructivefor irreversible ones.
| Prop | Type | Default |
|---|---|---|
childrenLabel; its width is reserved so loading and success never resize the button. | ReactNode | — |
confirmLabelTwo-step action: first click swaps the label to this, second click runs | ReactNode | — |
confirmTimeoutMs before an armed confirm step resets. Also resets on blur or Esc. | number | 3000 |
loadingDisables the button and shows a spinner before the label. | boolean | false |
loadingLabelLabel shown while loading, with the spinner. Defaults to | ReactNode | — |
sizeHeight and padding; "icon" is square, so give it an | "default" | "sm" | "lg" | "icon" | — |
statusIn-place feedback: | ButtonStatus | "idle" |
successLabelText shown on | ReactNode | — |
swapWhere the old label slides out to on a swap. Default "left". | SwapDirection | "left" |
variantFill and emphasis: one primary action per view, | "default" | "secondary" | "outline" | "ghost" | "destructive" | — |
- type ButtonStatus
- "idle" | "loading" | "success"
- type SwapDirection
- "left" | "right" | "up" | "down"
* required. Every component also takes className.