Disable submission controls when the browser fires a valid submit event—not merely when a button is clicked. That covers keyboard submission and allows native constraint validation to run first. Server-side idempotency is still required for payments and other important writes.
Last updated: September 26, 2026.
const form = document.querySelector('#checkout');
form.addEventListener('submit', () => {
if (form.dataset.submitting === 'true') return;
form.dataset.submitting = 'true';
for (const button of form.querySelectorAll('[type="submit"]')) {
button.disabled = true;
button.setAttribute('aria-disabled', 'true');
}
const status = form.querySelector('[role="status"]');
if (status) status.textContent = 'Submitting…';
});For a normal navigation submission, the page unloads after the controls are disabled. The dataset flag prevents other handlers from starting the same client action twice.
Handle asynchronous submissions
If JavaScript sends the request with fetch(), call event.preventDefault(), disable the controls, and re-enable them in finally when the request fails or the response allows another attempt. Keep a saved reference to the submitter when different buttons perform different actions.
Preserve accessible feedback
Disabled controls are removed from keyboard focus and may not be announced with context. Update a nearby status region, change visible button text, and keep error messages associated with the relevant fields. Do not disable controls on the button’s click event before the browser completes validation.
MDN’s submit-control reference shows the runtime disabled property. The existing Enter-key form guide explains keyboard paths.
Make the server operation idempotent
Two requests can still arrive because of retries, multiple tabs, network tools, or malicious clients. Generate an idempotency key for important operations, store it with the result, and return the same result when the key is reused. Use a unique database constraint so concurrent requests cannot create duplicate orders.
- Client lock: improves immediate user experience.
- Request key: identifies one intended operation.
- Database constraint: provides the final concurrency safeguard.