Improving accessibility of forms

3 mins |

October 31, 2020

Forms are an essential part of the web. Most of us would have built at least one form per app. For me, it has been one of the most difficult part in developing any app in web. Most of the applications we build would also have some kind of sign up/ login forms. Often they are not accessible and/or leaves a lot to be desired in terms of UX.

Let us try to understand this by creating a login form.

Bad

This is a not so good version of a login form. It is not accessible nor user friendly. Form won’t submit if you hit return key, it won’t suggest email in the email field. And in a real world app, it won’t even prompt to save password.

Go ahead and try it yourself! It is interactable!!! 🥳

Login

Email
Password
Login

1function Form() {
2 const [state, setState] = React.useState({})
3 function handleChange(name, value) {
4 setState(current => ({ ...current, [name]: value }))
5 }
6 function handleSubmit() {
7 alert(JSON.stringify(state))
8 }
9 return (
10 <div>
11 <h3>Login</h3>
12 <div>
13 <span>Email</span>
14 <input
15 onChange={event => handleChange("username", event.target.value)}
16 />
17 </div>
18 <div>
19 <span>Password</span>
20 <input
21 type="password"
22 placeholder="password"
23 onChange={event => handleChange("password", event.target.value)}
24 />
25 </div>
26 <div onClick={handleSubmit}>Login</div>
27 </div>
28 )
29}

And here how it would look in action. This is not an exaggerated code. The above code sample is what I have seen in a real-world application. This is how NOT to build a form.

This form is broken (not accessible at all), let us make this form work. We need some basic functionality like suggest emails, accessible by keyboard and screen readers, ability to submit form by hiting return key etc.

Good

To improve accessibility and UX. We can achieve this by using semantic elements, instead of using <div>’s for everything.

Let’s use <form> to wrap our forms, <label> for labels of input (placeholders are not enough), and <button> for buttons. This might seem very obvious, but there are plenty of websites which don’t follow this.

Go ahead and try it yourself!

Login


1function Form() {
2- const [state, setState] = React.useState({})
3- function handleChange(name, value) {
4- setState(current => ({ ...current, [name]: value }))
5- }
6 function handleSubmit(event) {
7- alert(JSON.stringify(state))
8+ event.preventDefault()
9+ const { elements } = event.target
10+ const { email, password } = elements
11+ alert(JSON.stringify({ email: email.value, password: password.value }))
12 }
13
14 return (
15- <div>
16+ <form onSubmit={handleSubmit}>
17 <h3>Login</h3>
18 <section>
19- <span>Email</span>
20+ <label htmlFor="email">Email</label>
21 <input
22- placeholder="E-Mail"
23- onChange={event => handleChange("username", event.target.value)}
24+ id="email"
25+ name="email"
26+ type="email"
27+ required
28 />
29 </section>
30 <section>
31- <span>Password</span>
32+ <label htmlFor="password">Password</label>
33 <input
34 type="password"
35- placeholder="password"
36- onChange={event => handleChange("password", event.target.value)}
37+ id="password"
38+ name="password"
39+ required
40 />
41 </section>
42- <div onClick={handleSubmit}>Login</div>
43+ <button id="login" type="submit">
44+ Login
45+ </button>
46- </div>
47+ <form>
48 )
49}

Both these forms look very similar. But they are miles apart when it comes to accessibility. Now input has proper labels, screen readers can now read labels correctly. And we get tons of bonus features too 🕺.

  • When we click/tap on the label, the corresponding input is focussed.

  • We don’t have to maintain state value if we don’t need to. We can get all field values from event.target.elements[NAME_OF_THE_FIELD]

  • We can submit the form by hitting the enter/return key.

Also we can get browsers to prompt save password. To get that working, we should let the browser know that the form submit was successful. We can do that by either navigating to a new page. Or by calling history.pushState or history.replaceState and <form> needs to be removed from the page.

Hold on, that’s not it. We can make it better. 🤯

Better

We can improve this by letting the browser know what kind of value we are expecting from these fields. For that we can use autocomplete attribute.

Go ahead and try it yourself!

Login


1function Form() {
2 function handleSubmit(event) {
3 event.preventDefault()
4 const { elements } = event.target
5- const { email, password } = elements
6+ const { email, "current-password": password } = elements
7 alert(JSON.stringify({ email: email.value, password: password.value }))
8 }
9 return (
10 <form onSubmit={handleSubmit}>
11 <h3>Login</h3>
12 <section>
13 <label htmlFor="email">Email</label>
14 <input
15 id="email"
16 name="email"
17 type="email"
18+ autoComplete="username"
19 required
20 />
21 </section>
22 <section>
23 <label htmlFor="password">Password</label>
24 <input
25- id="password"
26- name="password"
27+ id="current-password"
28+ name="current-password"
29+ autoComplete="current-password"
30 type="password"
31 required
32 />
33 </section>
34 <button id="login" type="submit">
35 Login
36 </button>
37 </form>
38 )
39}

Autocomplete makes the UX a lot better. Few other values which might be helpful for sign-up. for eg.

1// for new passwords
2 <input
3 // ...other fields
4 autoComplete="new-password"
5 />
6
7 // for OTPs
8 <input
9 // ...other fields
10 autoComplete="one-time-code"
11 />

new-password is interesting and required because it wouldn’t prefill existing password in password reset scenario or if someone else is trying to sign-up from the same machine. And the cool thing about new-password is modern browsers will suggest secure passwords.

autocomplete is a nice feature which we forget to use most of the time. This helps in assisting the user in filling out the form reliably (avoid typos) and faster. There are around ~50 possible values for auto-complete. Check them out at MDN.

There are a lot of things we can improve. Listing down a few

  • use aria-describedby to describe any specific constraint on the field. eg. Password has to 8 chars.
  • use autofocus in the input, if that is the first input of the form that user would be filling etc. eg. username in our login form.
  • other UX improvements like size, the contrast of the text. Would recommend checking Web Vitals. Only add login if your application really needs it.

TL;DR

Use semantic HTML; form for forms, button for buttons etc. Make it accessible by having proper labels (aria properties too). UX can be improved by using the autocomplete attribute of the input.

This might seem very small, but we often miss this. Let us check the apps we have developed are accessible or not. We should try to make our apps more accessible. By far this is not enough but this is a good start.

I have focussed on Login/Sign-Up forms, but this can be translated to any forms you make. Drop-offs in Sign Up could be drastically improved with these minor changes. Let me know if this helped your application.


Got a Question? Bala might have the answer. Get them answered on #AskBala

Edit on Github

Subscribe Now! Letters from Bala

Subscribe to my newsletter to receive letters about some interesting patterns and views in programming, frontend, Javascript, React, testing and many more. Be the first one to know when I publish a blog.

No spam, just some good stuff! Unsubscribe at any time

Written by Balavishnu V J. Follow him on Twitter to know what he is working on. Also, his opinions, thoughts and solutions in Web Dev.