Components & actions
Loading states on buttons
Buttons that trigger async actions should show a spinner/loading state and disable re-click. Never leave a button looking clickable during submission.
spinner + disabled — the button is honest about working
still clickable — double-submit roulette
Spinner + disabled during submit
Static button allows double-click
How to apply it
- 1
Spin on any submit that changes server state — an order, a payment, a save — where a second click would fire a second request. A cheap reversible toggle has nothing to double, so it needs no spinner.
- 2
Put the spinner beside the label rather than replacing it, or reserve the resting width, so the button does not collapse mid-submit.
- 3
Disable the button for the life of the request and set aria-busy, so the state is announced and not only drawn.
- 4
Restore the resting state on failure as well as success, and surface the error next to the button.
Why it works
A static button during an async submit gives no feedback that the click registered, so users interpret silence as failure and click again, firing duplicate requests. A spinner plus disabled state closes that feedback loop immediately, showing the system received the action and is working on it.
What breaks
A save button that stays static and clickable during submission invites a second click before the first request resolves, creating duplicate records or double-charged actions on the backend.
Review questions
- 1
Does the button show a spinner while the request is in flight?
- 2
Is the button disabled during submission, so a second click cannot fire?
- 3
Does the button return to its resting state on failure as well as on success?