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):
<label for="firstname">First name</label> <input type="text" id="firstname" />
<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).
<label>
Email address
<input type="email" />
</label>
<label>
Quantity
<input type="number" />
</label>
<label>
Password
<input type="password" />
</label>
<label>
Website URL
<input type="url" />
</label>
<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 identify 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"
!
<label>
Password
<input type="password" autocomplete="new-password" />
</label>
<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).
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 anaccept
attribute that lets us set the formats the input should accept (.png, .jpeg, .doc, .pdf, etc)number
inputs can have amin
and amax
valuetel
inputs can have aminlength
and amaxlength
<label>
Profile picture
<input type="file" accept="image/png, image/jpeg" />
</label>
<label>
Quantity
<input type="number" inputmode="numeric" min="1" max="10" />
</label>
<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>