UX tip graphic with the title 'Show button status to prevent repeat clicks.' Two checkout summaries stacked: the top marked with a red X shows two identical 'Order details' cards with $110 USD total and a 'Pay $39' button — the second card shows the same button being tapped again with a cursor, illustrating a duplicate click. The bottom marked with a green checkmark shows three sequential 'Order details' cards: the first with the default purple 'Pay $39' button being clicked, the second showing a disabled 'Processing' button with a spinner, and the third showing a green 'Success' button with a checkmark — making clear the order resolves into a clear final state. BRIX Templates branding at the bottom.

Status & feedback

A button that doesn't react to taps gets tapped again — and again

Buttons that don't show progress invite repeat taps and duplicate submissions. Use loading and success states so users see their action was received.

How to use button loading states to prevent duplicate clicks

When a submit button gives no visual feedback after a tap — same color, same label, same shape — users genuinely can’t tell whether their action was received. Their natural response is to tap again, sometimes several times, which can produce duplicate submissions, double charges, or repeated form posts. Even when the backend dedupes the requests, the user experience is one of uncertainty: “Did that work? Should I try again?” That doubt frequently turns into support tickets, refund requests, or abandoned attempts.

A more reliable pattern is to transition the button through clear visual states that mirror the underlying action. The default state shows “Pay $39”; the moment the user taps, the button enters a loading state — disabled, with a spinner and a label like “Processing” — and finally resolves into a success state with a checkmark and a confirmation color. The user sees the action’s progress as it happens, so the question “did it go through?” never needs to be asked.

Disable the button as soon as it’s tapped so it can’t fire a second request, and pair the disabled state with an obvious visual change. Show a spinner inside the button during processing — the loading indicator should travel with the action, not float somewhere else on the page. Resolve into an explicit success state for high-stakes actions (payments, account changes) instead of relying on a page redirect alone. Use a different color for the success state — typically green — so the outcome registers at a glance.

  • Define three button states at minimum: default, loading (disabled + spinner), and success or error.
  • Disable the button while the request is in flight to prevent duplicate submissions at the UI layer.
  • Place the spinner inside the button so the feedback is tied to the element the user actually tapped.
  • Use distinct colors per state — primary for default, muted for loading, green for success, red for error.
  • Pair UI deduping with server-side idempotency so the user can’t double-charge even if the disabled state fails.

Button loading and success states can prevent the duplicate-submission problem and remove the doubt that follows every tap of a high-stakes CTA. When the button clearly shows that an action was received, processed, and completed, users typically stop second-guessing the interaction — because the page is already answering the question they were about to ask.

Frequently asked questions

What states should a submit button have?

At minimum: default (idle and tappable), loading (disabled with a spinner or label like 'Processing'), and success or error (a final visual confirming the outcome). For payment and high-stakes actions, an explicit success state is essential — users need to see that something completed, not just that the page changed.

Should I disable the button while it's loading?

Yes. A disabled state prevents the duplicate-submission problem at the UI layer and pairs with a server-side idempotency check for safety. Make sure the disabled state is visually distinct (lower opacity, no hover) so users see why nothing happens when they tap again.

How long should the loading state remain visible?

As long as the action is actually processing. If the request resolves in under 100ms, the spinner can flash and feel buggy — consider showing the success state directly. If it takes more than two or three seconds, show progress text ('Processing payment…') so users understand the delay isn't a hang.

What about optimistic UI — show success before the server confirms?

Use it carefully. Optimistic states work well for low-stakes actions like favoriting, liking, or saving a draft. For payments, account creation, or anything users would notice if it silently failed, wait for the server confirmation before showing the success state. The cost of a false success is much higher than the cost of a half-second wait.