Disable the submit control as soon as it is used
Aliases: double submit · disable on submit · duplicate submit
What it is
While waiting for a result, people press the same submit control again—slow network, no immediate button state, Enter repeating from the keyboard. Immediate disable means: on the beat the first submit is accepted, the primary button, Enter-to-submit, and any “Save” in a menu all become unable to fire again until this request ends. The control is disabled; the form is not frozen into invisibility. This entry is only about blocking a second send after a successful first send. It is not about opening the door again after failure. An idempotent API can swallow duplicates on the server; it cannot stop people from thinking the first click missed and creating two orders.
Why it happens
Between submit and response there is a blank. The model is “the button did not move = it did not take.” The longer the blank, the more likely a second click. A primary action that still looks pressable is an invitation to press again. A second layer is multi-channel firing. A mouse click, a touch end, keyboard Enter, and an IME confirm key can each send once within tens of milliseconds. Setting the button to disabled while Enter is still bound to form.submit leaves the second channel alive. Disable must happen when the event is accepted, not when an animation ends or the server answers—those tens to hundreds of milliseconds are the double-click window. Loading copy (“Submitting”) explains the blank; it does not replace disable. If people can read “Submitting” and still press, they treat it as a progress bar and poke it.
Studying it
Watch double presses under artificial delay (1 s, 3 s, 8 s). Compare no feedback and still pressable, copy change still pressable, and immediate disable. Include keyboard Enter and double-tap.
Independent variables: whether disable is synchronous on the first event, delay length, whether Enter still submits, whether button copy switches to in-progress. Dependent variables: requests produced by one intent, duplicate orders or duplicate posts, reports of “it didn’t take.”
Lab instructions to “click only once” suppress doubles. Use a natural instruction: wait for a result after clicking; press again if it seems to have done nothing. Server-side dedup succeeding is not evidence the control stayed enabled—the person already issued two intents.
Where it stops holding
A draft “Save” that may be pressed after every few keystrokes is not this pattern; that is another write, not a repeat of one intent. Offline queues may leave the control available so changes can enter the queue, but one intent may only have one entry in that queue. Assistive-technology users rely on a focusable button to hear state; after disable, a live region must announce “submitting,” or focus vanishes in silence. Automatic retries are not the user’s second submit; the control stays disabled until the whole attempt ends.
Applying it
- On the first line of the submit handler, synchronously disable every submit channel: primary button, secondary “Save and close,” Enter on the form, and double-click.
- At the same time, switch the button to in-progress copy or a waiting state so the blank is read as “accepted,” not “missed.”
- Do not wait for the network callback to disable. Animation and the request may run in parallel; closing the control must be synchronous.
- Verify by delaying the API to 3 seconds, mashing the button and hitting Enter: the network panel must show one request. Submit with Enter only from the keyboard and confirm later Enters are swallowed. Repeat with a double-tap on a touch screen.