Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Buttons

Pro

Success Button for React

A button that goes from its label to a spinner to a check that draws itself in, to say an action is done. For React with Base UI, when a save or a send needs a confirmation without a toast.

Framework
UI library

Drive it with state: idle shows the label, loading a spinner with Loading…, and success a check with Done. The check pops in and draws itself. After resetDelay, two seconds by default, it calls onStateChange("idle") so you can set it back.

Use it for actions people want confirmed in place: saving settings, adding to a cart, sending an invite. For a long list of results or an error, a toast or an inline message says more; for a wait with no confirmation, the Loading Button is enough. It wraps the Button, so variant and size work as usual. The rows share one place and swap with CSS transitions, and the check's stroke is a CSS keyframe animation the install adds to your stylesheet, with no animation library; with reduced motion the rows only fade and the check appears whole.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import { SuccessButton } from "@/components/ui/success-button";
<SuccessButton
  onClick={save}
  onStateChange={setState}
  state={state}
  successLabel="Saved"
>
  Save changes
</SuccessButton>

Demos

Color

successColor colors the check, any CSS color; by default it takes the label's color. Here an outline button's check turns green once the item is in the cart.

Preview width

Staying Done

resetDelay={0} keeps the check until you set state back to idle yourself. It suits an action that happens once, like sending an invite.

Preview width

Accessibility

The spinner, the check and their rows are hidden from assistive tech; the label at rest keeps naming the button. A polite status inside the button says loadingLabel when it starts and successLabel when it's done. While loading, the button is marked aria-disabled and ignores presses, but keeps its focus.

Enter
Activate the button, unless it's loading.
Space
Activate the button, unless it's loading.

FAQ

How do I show the check?

Set state="loading" when your request starts and state="success" when it resolves. The button doesn't run the request; it shows where it is.

Does it go back to the label by itself?

It asks to: resetDelay milliseconds after success, 2000 by default, it calls onStateChange("idle"). Pass your state's setter there. With resetDelay={0}, the check stays until you change state.

Can I change Loading… and Done?

Yes, with loadingLabel and successLabel, such as Saving… and Saved. The button is as wide as the widest of the three rows, so it doesn't change size between them.

Can the success state use another color?

successColor colors the check, such as var(--color-emerald-600). The rest keeps the variant's colors, so the label stays readable on every variant. The Color demo uses it on an outline button.

Can it be pressed while it loads?

No. While state is loading it's aria-disabled, presses are ignored, and it stays focusable. In the success state it can be pressed again. Your click handler isn't called while it loads.

Do I need to add keyframes to my global CSS?

The install does it: the CLI adds the success-button keyframes and their animate-success-button utility to your stylesheet. Copying the file by hand, add the CSS shown under Manual in Installation.

What happens with reduced motion?

The rows fade without sliding or popping, and the check shows whole instead of drawing itself.

API Reference

SuccessButton

data-slot="success-button-label"

PropType
state?SuccessButtonState

Where the action is: at rest, running (a spinner, and presses stop) or done (a check that draws itself). You set it from your request.

Default
"idle"
onStateChange?(next: SuccessButtonState) → void

Called with "idle" when the check has stayed resetDelay milliseconds.

Type
(next: SuccessButtonState) → void
Parameters

next – SuccessButtonState

Returns
void
children?ReactNode

The label at rest; it names the button in every state.

focusableWhenDisabled?boolean

Whether the button should be focusable when disabled.

Default
false
loadingLabel?ReactNode

The label while state is "loading", beside the spinner.

Default
"Loading…"
nativeButton?boolean

Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (for example, <div>).

Default
true
resetDelay?number

How long the check stays before onStateChange("idle"), in milliseconds; 0 keeps it until you change state.

Default
2000
successColor?string

The check's color, any CSS color. By default the label's color.

successLabel?ReactNode

The label once state is "success", beside the check.

Default
"Done"
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-state?string

Mirrors state.

CSS variableType
--success-button-colorstring

The check's color (the label's by default).

All 35 buttons