Briefly
A form is an important element of the interface. It is important to make it accessible, as forms can be visually and cognitively complex. They can confuse the user, causing them to leave the site. Accessible forms are easier to use for everyone, including people with disabilities.
Making a form accessible can be aided by its logical structure and ease of use, user awareness of errors and data formats, as well as proper markup. For example, a form must have labels associated with its fields <label>
.
General Principles of Form Accessibility
User understands the purpose of fields
Each field must have an associated label in the <label>
element that describes its purpose. This visually connects the text to the form control and is read by screen readers.
In addition, clicking on a <label>
transfers focus to the associated field, making it a larger target. Therefore, it is recommended to use <label>
for field labels instead of title
and placeholder
.
Semantic HTML tags are used
Native HTML tags have many useful built-in functions. Let’s break this down with an example.
<form> <label for="firstname"> Name </label> <input type="text" id="firstname" name="firstname" required> <div for="surname"> Surname </div> <div id="surname" contenteditable></div> <button type="submit"> Save </button></form>
<form> <label for="firstname"> Name </label> <input type="text" id="firstname" name="firstname" required> <div for="surname"> Surname </div> <div id="surname" contenteditable></div> <button type="submit"> Save </button> </form>
If you click on the label "Surname," the field will not be focused. The <label>
and the <input>
field are associated with each other via the id
and for
attributes, so the focus trick will work.
Also, screen readers do not recognize <div>
(and <span>
too) as an input element by default. The user will not be able to fill out such a field without additional effort—ARIA, JavaScript, and the contenteditable
attribute. In this example, the user will only hear the text "Surname" and will not know that the element is a field for the surname.
Another downside of <div>
is that the data entered into it will not be included in the request after submitting the form data. Yes, technically, data can be submitted using JavaScript, but <input>
does this by default.
Another non-obvious problem with <div>
is that the inputmode attribute does not work with it. However, if <input>
has a type
or inputmode
specified, the appropriate set of characters will be displayed on devices with a touchscreen keyboard when entering data into the field.
Firstly, it is necessary to show the user which specific element in the form is active. This can be easily achieved with the pseudo-classes :focus
or :focus
.
Secondly, the visual order of the elements should match their order in the DOM. That is, the order of focus should align with the order of reading and sequentially move left to right, right to left, or top to bottom depending on the interface language. Therefore, try not to use order
.
Thirdly, if there are many fields in the form, it is best to break the form into several sequential steps. This will make working with the form easier and the user will not feel overwhelmed.
Fourthly, when filling in one of the form fields, the focus should not switch to the next field. The user should control this process themselves, as their data may vary.
User knows about data formats
It is good to give users clear instructions for filling out the form, especially if it is complex. Let’s look at an example. Suppose a field must contain at least 8 characters, and we have a check for this.
<form> <label for="name">Name (required)</label> <input required minlength="8" type="text" id="name" name="name" autocomplete="given-name" aria-describedby="hint" > <span id="hint">Name must be at least 8 characters long.</span> <button type="submit">Save</button></form>
<form> <label for="name">Name (required)</label> <input required minlength="8" type="text" id="name" name="name" autocomplete="given-name" aria-describedby="hint" > <span id="hint">Name must be at least 8 characters long.</span> <button type="submit">Save</button> </form>
To inform the user about the minimum number of characters, you can add a hint about the expected format before or below the field. To associate the hint with the field for screen readers, use the aria
attribute. Attach an attribute with the necessary text value to the <input>
field, and an id
with the same value to a wrapping <span>
for the hint text.
You can also add general instructions for filling out the form before all the form fields, and detailed instructions can be provided via a link wrapped in an <a>
.
User knows about errors and success
It is important to communicate errors and the success of form filling to all users. For example, when a user enters invalid data into a required field with the required
attribute, an error message will appear upon form submission. It is important to place the error message visually next to the field it relates to, as well as to inform it to screen readers.
After successfully filling out the form and submitting it, it is good to display a success message.
A few recommendations for error validation:
- Write simple and clear error texts that state what needs to be fixed and how.
- Simplify the task of correcting the error. This can be done by focusing on the field with the error when clicking on the error message.
- Use the
aria
attribute on fields with errors. It informs screen readers that the field contains an error.- invalid = "true" - Link error texts with fields using
aria
or- describedby aria
.- errormessage aria
currently has poor support.- errormessage - Ensure that error messages are automatically read by the screen reader. This can be done using
role
or= "alert" aria
for the error message, so it is read immediately.- live = "assertive" - Let the user return to the field where the error occurred. You can use a link or button for this.
- An error should occur when submitting the form data, not when the user starts entering data or focuses.
- Hints and minor messages should not look like errors or important warnings.
- Errors in incorrectly filled form fields should be marked with text or icon, in addition to color.
- Consider regional features associated with names and surnames (for example, some people have three names somewhere, while others have short surnames).
You can find more tips in the article on form validation on Smashing Magazine.
The main form control element is <input>
.
It is important to specify the correct type for fields in the type attribute. Let’s go through the main types found in forms.
text
Use type
for single-line text input fields. This type is actually the default for all fields without an explicitly specified type
.
<label for="name">Enter your name:</label><input id="name" type="text" autocomplete="given-name">
<label for="name">Enter your name:</label> <input id="name" type="text" autocomplete="given-name">
Matching values in for
and id
associate the label with the form control. Since id
values must be unique on each page, a field can only have one associated <label>
.
You can also create an association by enclosing the label text and input data within a <label>
element (not using for
and id
).
checkbox
Use type
for checkboxes.
<fieldset> <legend>Select a topping:</legend> <input id="bacon" type="checkbox" name="toppings" value="bacon"> <label for="bacon">Bacon</label> <input id="pepperoni" type="checkbox" name="toppings" value="pepperoni"> <label for="pepperoni">Pepperoni</label> <input id="mushrooms" type="checkbox" name="toppings" value="mushrooms"> <label for="mushrooms">Mushrooms</label> <input id="olives" type="checkbox" name="toppings" value="olives"> <label for="olives">Olives</label></fieldset>
<fieldset> <legend>Select a topping:</legend> <input id="bacon" type="checkbox" name="toppings" value="bacon"> <label for="bacon">Bacon</label> <input id="pepperoni" type="checkbox" name="toppings" value="pepperoni"> <label for="pepperoni">Pepperoni</label> <input id="mushrooms" type="checkbox" name="toppings" value="mushrooms"> <label for="mushrooms">Mushrooms</label> <input id="olives" type="checkbox" name="toppings" value="olives"> <label for="olives">Olives</label> </fieldset>
A group of checkboxes is nested inside a <fieldset>
, and a <legend>
adds a label to it. A group of interactive elements should always have a label. Screen readers announce the group's label when interacting with its elements, so the text should be concise and descriptive.
Single checkboxes with associated labels in <label>
do not need to be wrapped in <fieldset>
with <legend>
.
Nested <fieldset>
elements may cause strange behavior with screen readers, so avoid this.
email
Use type
for email fields, not text
. This allows browsers to correctly display email when autocomplete is enabled, validate it by default, and make it easier for users to enter addresses on mobile devices with a virtual keyboard.
For email fields, also use autocomplete
with the value email
. Do not force users to enter their email every time.
<label for="email">Enter your email:</label><input type="email" name="email" id="email" autocomplete="email">
<label for="email">Enter your email:</label> <input type="email" name="email" id="email" autocomplete="email">
password
Use type
for password fields. It is also a good idea to use the autocomplete
attribute with the value new
if it is a new password, or current
if it is the current one.
<label for="userPassword">Enter your password:</label><input id="userPassword" type="password" autocomplete="current-password">
<label for="userPassword">Enter your password:</label> <input id="userPassword" type="password" autocomplete="current-password">
radio
Use type
for radio buttons.
<fieldset> <legend>Choose delivery time:</legend> <input id="night" type="radio" name="shipping" value="night"> <label for="night">Night</label> <input id="day" type="radio" name="shipping" value="day"> <label for="day">Day</label> <input id="morning" type="radio" name="shipping" value="morning"> <label for="morning">Morning</label></fieldset>
<fieldset> <legend>Choose delivery time:</legend> <input id="night" type="radio" name="shipping" value="night"> <label for="night">Night</label> <input id="day" type="radio" name="shipping" value="day"> <label for="day">Day</label> <input id="morning" type="radio" name="shipping" value="morning"> <label for="morning">Morning</label> </fieldset>
<select>
Dropdown lists are a complex pattern. For simple cases, use <select>
.
<label for="favCity">Choose your favorite city:</label><select id="favCity" name="select"> <option value="amsterdam">Amsterdam</option> <option value="paris">Paris</option> <option value="rome">Rome</option></select>
<label for="favCity">Choose your favorite city:</label> <select id="favCity" name="select"> <option value="amsterdam">Amsterdam</option> <option value="paris">Paris</option> <option value="rome">Rome</option> </select>
Long lists of options can be grouped using <optgroup>
within <select>
. Keep in mind that sometimes screen readers ignore <optgroup>
, so don’t rely on this method to convey important information.
Buttons
A form can have one or several buttons. They can be implemented in several ways.
<!-- Simple <button> button, it has a built-in role button --><button type="submit">Submit</button><!-- Buttons for submitting and resetting data with types submit and reset,they also have a built-in role button --><input type="submit" name="submit" value="Submit"><input type="reset" name="reset" value="Reset">
<!-- Simple <button> button, it has a built-in role button --> <button type="submit">Submit</button> <!-- Buttons for submitting and resetting data with types submit and reset, they also have a built-in role button --> <input type="submit" name="submit" value="Submit"> <input type="reset" name="reset" value="Reset">
Screen readers read the text nested within <button>
and the value
attribute of <input>
. Form buttons should never be empty.
<button>
must be nested inside a <form>
. Only in this case, the form data will be submitted when the Enter key is pressed.
Do not use reset buttons unless absolutely necessary, as they can easily be activated by mistake.
Form Control Element Attributes
Fields can have various attributes that define their behavior and appearance. The most important attributes are:
required
– the field is required.disabled
– the field is not available for input.readonly
– the field is read-only.maxlength
– the maximum number of characters that can be in the field.pattern
– a regular expression that the entered data must meet.autocomplete
– should the browser automatically fill in the form field based on previous user inputs. See allautocomplete
values.
Important "No's"
- Do not create custom elements if there are native tags and attributes in HTML.
- Do not make forms large and confusing. This complicates life for all users.
- Do not create forms that cannot be filled with a keyboard. Some users cannot use a mouse or other input devices.
- Do not use excessively small input fields or buttons. These may be difficult for users with disabilities to interact with.
- Do not use color alone to indicate required fields. Some users cannot distinguish colors.
- Do not use sound alerts solely to notify of errors or successful form submissions. There are users who cannot hear or understand sounds.
- Do not quickly clear data entered into fields unless it is a financial operation. Give users at least 24 hours.
- Do not disable autocomplete with
autocomplete
.= "off" - Do not allow users to accidentally submit form data multiple times.
You can learn more about working with forms and data validation in the article on “Working with Forms”.
<label>
and Alternatives
The <label>
element has a limitation. You cannot add multiple labels to one field or associate one label with multiple fields.
This limitation can be overcome using hidden <label>
elements and ARIA attributes:
It is also worth adding that <label>
can be:
- Implicit, when nested in another tag.
- Explicit or direct, when located next to another tag, and they are linked via
for
andid
.
In general, they are equivalent, but implicit labels may cause problems with screen reader declaration.
aria-labelledby
If a field has an associated <label>
and aria
, the screen reader will read the text from aria
instead of the <label>
.
aria-describedby
<label for="resetPass">Enter new password:</label><input type="password" id="resetpass" autocomplete="new-password" aria-describedby="passwordHint"><span id="passwordHint"> The new password must contain between 8 and 15 characters, including letters and numbers.</span>
<label for="resetPass">Enter new password:</label> <input type="password" id="resetpass" autocomplete="new-password" aria-describedby="passwordHint" > <span id="passwordHint"> The new password must contain between 8 and 15 characters, including letters and numbers. </span>
In this example, aria
is linked to the id
of the element with password requirements. The screen reader usually reads the text from <label>
, then the <input>
type (text
, checkbox
, etc.), any necessary properties of the field (required
, readonly
, etc.), and then the additional description.
With aria
, you can reference multiple elements. To do this, add multiple values to the attribute and separate them with spaces.
The screen reader reads the content of aria
in addition to the <label>
, so the text inside <label>
is also necessary.
aria
can also be used for additional information that is displayed as tooltips.
Invisible <label>
Sometimes a visible label in <label>
is not needed for fields. For example, single fields like search boxes are usually placed next to the search button, and their purpose is obvious to sighted users. Therefore, a visible label may be redundant. In such cases, use a hidden label. There are three ways to add it.
Use only one of these methods. Using two or more attributes simultaneously will lead to repeated information for the screen reader. For example, a hidden <label>
and a title
attribute.
Hide <label>
with CSS. Most often, the element is simply moved off-screen using the visual-hiding technique. This way, the label will not be visually displayed, but the screen reader will still read it.
<label class="visually-hidden" for="search">Search</label><input type="text" id="search">
<label class="visually-hidden" for="search">Search</label> <input type="text" id="search">
If the <input>
has a title
attribute but no <label>
, the screen reader will read the title
as the label. This will also cause a tooltip to appear when hovering over the field.
While a tooltip can be helpful, the title
attribute has more downsides. It will only be seen by mouse users and can be distracting for some. Therefore, it is better to use other methods to add a hidden label to the field.
<input id="search" type="text" title="Search">
<input id="search" type="text" title="Search">
aria
also overrides the value of <label>
.
<input id="search" type="text" aria-label="Search">
<input id="search" type="text" aria-label="Search">
Test Forms
Be sure to check the accessibility of your forms.
- Test with screen readers like NVDA, JAWS, VoiceOver, and others.
- Use special developer tools to check accessibility in Chrome and Firefox.
- Use automated accessibility testing tools. For example, axe or Lighthouse.
- You can also use an HTML validator.