Loading...

Skip to main content

Accessible form inputs in HTML

Writing semantic HTML is arguably the most important thing a developer can do to improve the accessibility of a website. User-friendly forms can often be tricky to design, especially if asking for a lot of information, so we need to make sure that the implementation is not creating more friction for our users.

The basics

First, we need to pair our input with a label. We can either wrap the label around the input, or use the for and id attributes.

Option 1 (label wrapped around input):

<label> First name: <input type="text"> </label>

Option 2 (the for attribute of the label matches the id of the form control):

HTML
<label for="firstname">First name:</label> <input type="text" id="firstname">
React
<label htmlFor="firstname">First name:</label> <input type="text" id="firstname" />

Associating inputs with labels is important for screen reader users (so they know what information to enter), but also for sighted users; remember that labels should always be visible, even if the input has a value. So don't use placeholders as labels!

The type attribute

In the example above, the input is for the user's first name, so we set the type as text. The type attribute lets the browser know what input to render and how the user can interact with it. Just because the value of an input is a string, it doesn't necessarily mean that the type should be text: it could also be email, password, url, or even color. The same thing applies to numbers: if we needed users to enter a phone number, we'd use tel instead of number. You can read more about input types in the MDN docs(Opens in a new tab).

email
<label> Email address: <input type="email" /> </label>
number
<label> Quantity <input type="number" /> </label>
password
<label> Password <input type="password" /> </label>
url
<label> Website URL <input type="url" /> </label>
tel
<label> Phone number <input type="tel" /> </label>

The required attribute

The required attribute marks the input as invalid when empty and prevents it from being submitted to the server.

<label> Phone number (required) <input type="tel" required /> </label>

Note that even if you are handling your form validation with JavaScript, you should still use the required attribute where relevant, as it will allow screen readers to announce the input as required. You can then style your inputs using the :valid and :invalid CSS pseudo-classes.

Your form validation logic should also add the aria-invalid attribute to invalid inputs so they can be properly flagged by screen readers and other user agents.

The inputmode attribute

If we set the type of an input, user agents might adapt the keyboard to the expected format; for example, the iOS keyboard for email inputs will typically include the "@" character. But that's not true for all input types; iOS just renders a regular keyboard for number inputs. In this case, we can use the inputmode attribute. We can set it to numeric to show the numeric keyboard with the digits 0-9, or we can set it to decimal to allow fractional numbers. Here's a good resource with screenshots of the different keyboards(Opens in a new tab)

<label> Phone number <input type="tel" inputmode="tel"> </label>

Note that unlike type, inputmode won't affect validation.

You can read more about the inputmode attribute in the MDN docs(Opens in a new tab).

The autocomplete attribute

The last thing we can do to help our users fill our forms more quickly is adding autocomplete support. The autocomplete attribute allows user agents to idenfity the format of the expected value for an input, and pre-fill the values accordingly. This makes forms more efficient for all users, especially users that are attention deficit, have cognitive impairments, reduced mobility, low vision, or blind users.

autocomplete allows us to get a lot more specific than by just using type: a name field might have an autocomplete value of name, given-name, additional-name, family-name, or even nickname. But all of these would use a regular text input type.

A password field can also have multiple autocomplete values: new-password will let user agents and password managers know to generate a new password for your website, while current-password will fill the field with an existing password.

Ever wondered how your phone can pre-fill a one-time-code that was texted to you? This requires just one line of HTML: autocomplete="one-time-code"!

new-password
<label> Password: <input type="password" autocomplete="new-password" /> </label>
one-time-code
<label> Authentication token: <input type="number" inputmode="numeric" autocomplete="one-time-code" /> </label>

The autocomplete attribute doesn't affect validation. You can read more about autocomplete in the MDN docs(Opens in a new tab).

⚠️ A quick note about internationalisation: the concept of "first name" and "last name" is not international. So if you can, avoid breaking the name down into components, and just use name (or username).

Additional attributes

Depending on the type of an input, there might be some additional attributes that we need to set. For example:

  • file inputs have an accept attribute that lets us set the formats the input should accept (.png, .jpeg, .doc, .pdf, etc)
  • number inputs can have a min and a max value
  • tel inputs can have a minlength and a maxlength
image
<label> Profile picture <input type="file" accept="image/png, image/jpeg" /> </label>
number
<label> Quantity <input type="number" inputmode="numeric" min="1" max="10" /> </label>
tel
<label> Phone number <input type="tel" inputmode="tel" minlength="8" maxlength="10" /> </label>

Some inputs will also accept the pattern attribute, which allows us to further restrict entered values so they also have to conform to a specific pattern, using a regular expression (RegEx).

<label> Phone number (in the form XXX-XXX-XXXX): <input type="tel" inputmode="tel" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}" /> </label>