How do you handle a button that triggers a slow action?
On this page
A button tied to a slow action needs three things in sequence: immediate acknowledgment that the tap registered, a busy state that prevents a second submission, and a clear success or failure outcome when the work finishes. The button has to keep speaking while the request runs, because the gap between tap and result is where users lose confidence. Silence after a tap is not neutral, it actively breeds repeated clicks, and every repeat is a second request, a duplicate order, a doubled charge. The button must narrate its own progress, not sit there inert while the system thinks.
The mechanism behind this is that a user reads the absence of feedback as the absence of action. When nothing changes after a tap, the reasonable conclusion is that the tap missed, so the natural response is to tap again, harder, more times, and the slower the action the more repeats it collects. The first defense is instant acknowledgment, a visual change within a fraction of a second that confirms the press landed, even before the work is done, because that early signal is what stops the second tap before it happens. The second is disabling or locking the button into a busy state, which removes the option to fire it again and so closes off duplicate submission at the source rather than hoping the backend deduplicates, since a frontend guard the user can see is more honest than a silent one they cannot. The third is resolution, because a button stuck spinning forever is its own failure, and the user needs to know whether to move on or try once more; a busy state with no defined end is just a prettier version of silence.
Picture a checkout button on a slow payment gateway. The neglected version says “Pay” and does nothing visible while the charge processes for four seconds, so the anxious shopper taps it three more times and ends up disputing two phantom charges. The handled version flips the button to “Processing” with a spinner the instant it is pressed, disables it so further taps do nothing, and then resolves to a confirmation screen on success or an inline error with a retry on failure. The shopper sees that the tap worked, cannot accidentally double-pay, and knows exactly where they stand when it ends.
There is one real limit: the busy state should communicate, not just block. Disabling the button stops double submission, but a button that merely greys out with no spinner or label leaves the user guessing whether the system is working or frozen, so the busy state needs a visible signal of ongoing work, not just an unresponsive control. For actions fast enough to complete in well under a second, this full treatment is overkill, and forcing a spinner onto an instant operation can read as artificial slowness, so reserve the heavier feedback for work that genuinely takes time.
When you wire up a slow-action button, make it respond the moment it is tapped, lock it into a visible busy state that rules out a second submission, and resolve it cleanly to success or a recoverable failure. Treat the silent button as a bug, because a control that goes quiet under load is a control that invites the user to break it. Give the button a voice for the whole duration of the work, and the slow action stops feeling broken even when it is genuinely slow.