Don't disable buttons

https://gomakethings.com/dont-disable-buttons/

disabled shouldn't block focus - danShumway

Today I want to talk about why developers do it, why it’s bad, and what you can do instead. Let’s dig in!

Why developers disable buttons #

Typically, I see the pattern used to prevent a form from being submitted a second time while waiting for the form is processed.

Often, you’re waiting for an API response that may take a few moments, and you don’t want the user to submit the form again until the original response is processed.

form.addEventListener("submit", function (event) {
  // Don't let the form reload the page
  event.preventDefault()

  // Get the submit button and disable it
  let btn = form.querySelector("button")
  btn.addAttribute("disabled", "")

  // Do more form stuff...

  // re-enable the button after the API responds
  btn.removeAttribute("disabled")
})

With the button disabled, it cannot be clicked again.

Why this pattern is bad #

For starters, it doesn’t really do what you want it to.

Just because the button is disabled doesn’t mean the form can’t be submitted. Someone could focus on one of the form fields, then press the enter or return key, and the form would submit.

But it’s also horrible for accessibility.

Elements with the disabled attribute cannot receive focus, which creates confusing situations for screen reader users and people who navigate the web with a keyboard.

If the button is the current item in focus when you add the disabled attribute (for example, if someone just pressed it to submit the form), the element actually loses focus, which shifts to the document element.

For a screen reader user, this is particularly jarring, as now you’re in a totally different place on the page.

So to recap, it doesn’t do what you actually want it to and it breaks your application for a segment of your users.

A better way #

So, what should you do instead?

I personally prefer to add an attribute to the form as its being submitted, and remove it after all of the form actions are done. Whenever a submit event happens, I check for that attribute first. If it exists, I end the event handler early to prevent multiple form submissions.

form.addEventListener("submit", function (event) {
  // Don't let the form reload the page
  event.preventDefault()

  // If the form is already submitting, do nothing
  if (form.hasAttribute("data-submitting")) return

  // Add the [data-submitting] attribute to stop multiple submissions
  form.setAttribute("data-submitting", "")

  // Do more form stuff...

  // Remove the [data-submitting] attribute
  form.removeAttribute("data-submitting")
})

This preserves focus on your form elements and avoids any weird accessibility issues, prevents duplication form submissions, and also prevents keyboard actions on other form fields from submitting the form.

You can also style form elements to “appear” disabled using the [data-submitting] CSS selector…

[data-submitting] button {
  opacity: 0.8;
}

Don’t forget to also include an ARIA live region and display form status messages throughout the process.