shadcn.io is not affiliated with official shadcn/ui
Set isLoading and the label rolls up and out while a spinner and loadingText, such as Saving…, roll in; set it back and the label returns. Without loadingText or spinnerPosition, it's the Button's own loading state: a spinner centered over the hidden label. While it loads, presses do nothing and the button keeps its width, so nothing around it jumps.
Use it for any action that waits on a request: saving a form, sending a message, syncing data. When the action should also say it's done, use the Success Button. It wraps the Button, so variant and size work as usual. The two rows share one place and swap with CSS transitions, with no animation library; with reduced motion they only fade.
Installation
Usage
import { LoadingButton } from "@/components/ui/loading-button";<LoadingButton isLoading={saving} loadingText="Saving…" onClick={save}>
Save changes
</LoadingButton>Demos
Spinner Position
spinnerPosition puts the spinner at the start of the label or its end; the end reads well for an action that moves forward, such as Continue. Without loadingText, the children stay beside the spinner. Both sides follow the reading direction, so they flip in a right-to-left page. The last button sets neither, and gets the Button's centered spinner.
Form
As a submit button, it shows the request is on its way and ignores more presses until it ends. That includes Enter in the field: the press it makes is stopped too, so the form isn't sent twice.
Accessibility
The spinner and the loading row are hidden from assistive tech; the label at rest keeps naming the button. When loading starts, a polite status inside the button says loadingText, or Loading without it. The button is marked aria-disabled and stays focusable while it loads, so a keyboard user who pressed it isn't sent back to the top of the page. With the centered spinner, it's the Button's own loading state, which is disabled and aria-busy.
- Enter
- Activate the button, unless it's loading.
- Space
- Activate the button, unless it's loading.
FAQ
How do I show Saving… while it loads?
Pass loadingText="Saving…". It sits beside the spinner and the status says it too.
Can the spinner go after the label?
Yes, with spinnerPosition="end"; start puts it before. Leave out both spinnerPosition and loadingText for a spinner centered over the label. The Spinner Position demo shows all three.
Do I need to disable it myself while it loads?
No. While isLoading is on, the button is aria-disabled and ignores presses, and it keeps its focus.
Your click handler isn't called while it loads.
Why doesn't the button change width?
The label and the loading row share one grid cell, so the button is as wide as the wider one in both states. A short label may look a little padded at rest; that's the space the spinner needs.
How is it different from Button's isLoading?
It's the same prop. With nothing else, Loading Button is the Button: a centered spinner over the hidden label. loadingText and spinnerPosition add what the Button lacks: a label beside the spinner, and a roll between the two rows.
API Reference
LoadingButton
data-slot="loading-button-label"
children?ReactNode
The label at rest; it names the button in both states.
focusableWhenDisabled?boolean
Whether the button should be focusable when disabled.
- Default
false
loadingText?ReactNode
The label while loading, such as "Saving…", beside the spinner. Leave
it and spinnerPosition out for the Button's own centered spinner.
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
spinnerPosition?"start" | "end"
Which side of the label the spinner sits on while loading: start is
the left in a left-to-right page, the right in a right-to-left one.
Leave it and loadingText out for the Button's own centered spinner.
- Type
"start" | "end"
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.
data-state?string
"loading" when loading is true, otherwise "idle".
Related
- Pro
Success Button
Progress & resultsAdd a check when it finishes
- Pro
Download Button
Progress & resultsShow real progress, not a spinner
Countdown Button
Progress & resultsWait before the next press
Copy Button
Progress & resultsInstant feedback, no waiting
- Pro
Shake Button
Motion & feedbackSay the action failed
- Pro
Long Press Button
ControlsHold to confirm a risky action