Doing forms correctly

Forms are incredibly complicated to get right from an accessibility standpoint. I don't know all the good practices but I've seen some bad ones that I can warn you about. Please contact me if you find a mistake in this section as I really am not 100% sure about everything.

Forms require strict HTML

With more people validating forms with JavaScript, we've seen a drop in code quality of forms. But the reality is quite simple: if you forget about some parts of the form, screen readers users will not know they are in a form to begin with!

  1. A form starts and ends with the <form> element even if you validate it with JavaScript. Also add an id to it.
  2. Each <input>, no matter its type, needs and id and a <label> with the for attribute pointing to it.
  3. If you have a multi-section form, regroup them with the <fieldset> element and add the <legend> element as the "title" of this fieldset.
  4. End your form with a <button> when you can, use the for attribute to point on the form's id, and use an aria-label to give more context.

For accessibility reasons:

Wrapping it up, here's an example that you can inspect with your browser's developer tools:

This is a form demo below, not a real one, don't activate it!
Choose your favorite monster

The kraken is a kind of giant squid.

The Sasquatch is a kind of giant ape.

The Mothman is a kind of giant moth.

That's it for the code, but more often than you think, bad forms are the result of non inclusive design practices, some of which we will see below.

Information below forms

Ever filled a form and right under the validate button, seen a wall of text written in 10 pixels size that is actually the terms and conditions? Well that can be a problem!

Most forms end with words like Continue or Validate and by doing so, suggest that the page is finished. A screen reader user can't know there's something else below.

This problem can be super tedious to explain to designers as most of them don't want a wall of text before the form, and don't ever suggest this to a marketing team! So an accessible solution is to simply use an invisible anchor link pointing to the wall of text right before the form starts.

Screen readers users are humans too, they dislike walls of text about legal stuff like anyone else! This solution allows them to either read it or even better, skip it, like we all do right?

Don't put text sections inside a form!

You probably already tried to tab between inputs in a form. That's because inputs are capturing elements that you can only escape by using the tab key. And when you do, the tab key will lead you to the next native focus-able element like another input, a button or a link.

It's important because too many designers put big explanation texts about the form between inputs. Since screen readers users can only use the tab key to get out of an input and will land on the next focus-able element, they will not have the explanation texts vocalized and have no way of access them. We saw that aria-describedby can be used to link to a descriptive text, but that's applying a bandage on a broken leg.

Removing texts sections inside forms might seem like a chore and, when talking about it with designers, they will probably tell you it's a going to make the whole experience bland or suffocating in the case of massive forms.

But the reality is that a form that needs explanation texts between inputs is a bad design right from the start. You should give all necessary information before filling the form, otherwise users might start filling it and realize in the middle they didn't have to, or are not concerned about it.

Your labels with the descriptions attached should be enough to make it a pleasant and understandable experience. Yes forms are not fun but we can't sacrifice usability to some funnel concept marketers enjoy to create.

Handling errors in forms is a pain

Handling errors in forms is super complicated and nobody seems to agree about how to do it correctly. It's especially hard with accessibility in mind, and not only because of screen readers. So take my advises with a grain of salt as I may be wrong on some cases and am still learning about this.

The first rule to respect is that triggering error messages should only happen either when the user tabs out of the input or when the user validates the form. Don't trigger error messages while the user is typing. Wait for him to go out of the input or it will be a vocalized nightmare or a panic inducing behavior. You might think that's counter-intuitive as screen readers users may not know that they wrote it wrong but that's actually OK if you do the following right.

When the user validates the form using the button, if an input is required and not filled or wrongly filled, move the user focus to this problematic input using the .focus() function in JavaScript. This way the user can promptly fill or fix it, then go back below super fast to validate the form again.

The error message should be added the same way as the instructions, with an aria-describedby. You can actually have two id as a value for aria-describedby, so screen readers will read the label, then the description, then the error message. Finally, a aria-invalid="true" needs to be added to each invalid input.

For the sake of the example, imagine we did not choose a monster below and tried validating the form. In JavaScript, we should add the error id to each input's aria-describedby so screen readers can vocalize the error message, and remove the display:none from the error message itself so non-screen readers users can see it too.

There is a form demo below, you can inspect it using the development tools.
Choose your favorite monsterError: you need to choose a monster.

The kraken is a kind of giant squid.

The Sasquatch is a kind of giant ape.

The Mothman is a kind of giant moth.

Because of all this and contrary to popular practices, it's forbidden to disable a form button, even if the user made errors or did not finish filling the form. If the validation button is disabled it disappears from vocalization and the user has to travel through the whole form to find which input is not good. This is less practical than sending him directly to the input when the button is clicked.

Applying this can be tricky on legacy applications that handle errors in a monolithic way. Even if you do front-end surface level error checking, the user might make a typo that can only be identified when the form is sent and the back-end discovers it by using information stored in a database. If this happens, the error handling in the back-end needs to be written in a way that returns which information is wrong, to allow the front-end to .focus() on the wrong input.

If the back-end returns a vague message then the front-end can't do anything outside using .focus() to move the user to a standard error message, and the user has to redo the entire form and find by himself what he did wrongly. This is a super bad user experience even for non-disabled users.

The best solution is of course to do error checking on the back-end on each input with an ajax call right after the user exits it. But for various reasons going from time to cost of requests, it's not always possible.

Good luck with your forms and more than anything, test your forms with real users, disabled or not, to find out if everything is ok. On this particular subject, you can't trust yourself, your client, product owner, designer or common tester. You need real, fresh experience.

Next: Tables are not that complicated

Initially published: November 23rd, 2020
Generated: November 12th, 2021