Structuring accessible forms

Last updated: 27 January 2023

Forms have a strong semantic structure from labeling controls to ensuring the states of controls are announced by assistive technology. We look at the basics of building a form with different input types, adding in the necessary HTML attributes to link our form elements semantically, sprinkling in ARIA attributes and data validation and errors.

Below is the Code Pen for my accessible form example. Access the code in the GitHub repository.

See the Pen Accessible Forms by Rachele (@racheleditullio) on CodePen.

Creating our form fields

The two basic parts of a form field are its <label> and <input> elements. We semantically link the two by

  • Using the <label for="uniqueID"> attribute which matches the <input id="uniqueID"> attribute; or
  • Making the <input> element a child of the <label> element

Doing so allows screens readers to announce the field’s <label> when the <input> gets focus. It also enables the <label> as an additional hit area when using the mouse to focus on an <input>, which is especially useful for checkboxes and radio buttons.

We also need to specify the type of input using the <input type=""> attribute. Putting those together, we have a basic form with one text field and two radio buttons.

<form>
  <label for="name">Cat's name</label>
  <input type="text" id="name" name="name">

  <label><input type="radio" id="yes"> Yes</label>
  <label><input type="radio" id="no"> No</label>

  <button type="submit">Send</button>
</form>

A note on comboboxes

As much as possible, use native HTML <select> and <option> elements to create an expandable list of single-select options.

...
<label for="markings">Cat's markings</label>
<select name="markings" id="markings">
  <option value=""> select an option </option>
  <option value="solid">Solid</option>
  <option value="bi">Bi-color</option>
  ...
</select>
...

autocomplete attribute

Part of structuring an accessible form is providing information about the type of data required by the field and providing autocomplete options if available. We do this by adding the autocomplete attribute to form fields asking for personal information.

<input type="email" id="email" name="email" autocomplete="email">

Below are some common examples of fields asking for personal information and their autocomplete values. Reference the full list of autocomplete values.

  • First name: given-name
  • Last name: family-name
  • Email: email
  • Username: username
  • Company: organization
  • Zip code: postal-code

If autocomplete information is available in the browser, fields with the autocomplete attributes will offer suggestions.

Autocomplete suggestion for an email field in Chrome

Grouping similar fields

Our radio buttons are labeled ‘yes’ and ‘no,’ but what are they for? We need to use the <fieldset> and <legend> elements to provide that semantic meaning. The <fieldset> groups the related form fields while the <legend> provides a name for the grouping. These are most often used for checkboxes and radio buttons but they can be useful for grouping other related fields, e.g. Shipping address.

Since radio button options may have similar names, group like radio buttons with the name="groupName" attribute. Keyboard users move between radio buttons in a group with the arrow keys.

...
<fieldset>
  <legend>Is your cat altered?</legend>
    <label><input type="radio" name="altered" id="yes"> Yes</label>
    <label><input type="radio" name="altered" id="no"> No</label>
</fieldset>
...

Marking required fields

Provide a visible indication of the required fields for sighted users, such as an asterisk. Include text before the form indicating how users can recognize required fields. See Kitty Giraudel’s article The required fault in our stars for a discussion on ways to denote required fields. Research from Baymard suggests e-commerce sites need to mark both required and optional fields.

Then there are two attributes we can use to programmatically define required fields: aria-required="true" and required. Use one (not both) of these attributes to ensure required fields are conveyed appropriately to assistive technology. Screen readers will say required when announcing the form field name, role and state.

...
<label for="name">Cat's name*</label>
<input type="text" id="name" name="name" required>

<label for="email">Your email*</label>
<input type="email" id="email" name="email" autocomplete="email" aria-required="true">
...

The required attribute provides a first layer of form validation on submit. Many modern browsers will not submit a form with empty required fields and will even provide a visible error message.

Required field in Chrome

However, user agent error handling is not dependable as these error messages are not always announced or available to assistive technology; they can’t be styled or resized; and they are not persistent. To disable browser validation testing, include the novalidate attribute in the <form> element.

Verifying data

In addition to marking fields as required for assistive technology, we also have to indicate if the data entered into each field is valid. All required fields should also include the aria-invalid="true" attribute added with JavaScript once the form has been submitted. Remove this attribute once valid data is entered.

We can further enforce data validation in HTML for fields that aren’t necessarily required by using the pattern attribute that accepts a regular expression. Let’s create a field for collecting birthday with a required format of mm/dd/yyyy.

...
<input type="text" id="birthday" name="birthday" pattern="^\s*(1[012]|0?[1-9])\/(3[01]|[12][0-9]|0?[1-9])\/((?:19|20)\d{2})\s*$" aria-describedby="error4">
<p id="error4">Birthday format mm/dd/yyyy</p>
...

This pattern matching prevents the form from submitting if the data entered does not match the specified pattern.

A form field that fails pattern validation in Chrome

Ensure users of assistive technology are aware of the required pattern by programmatically linking the help text with the form field using the aria-describedby attribute.

inputmode attribute

We can make it easier for people entering the date into the form field on mobile devices by adding the inputmode attribute. This attribute allows us to specify which virtual keyboard is displayed in supported browsers. Since dates are numeric, we can use inputmode="numeric" to display the numbers keyboard which provides fewer options and larger hit areas for each key.

screen shot of a mobile website form with the cursor in a field called Cat's birthday while the numeric virtual keyboard displays below.
Numeric virtual keyboard on iOS

Note: In my demo, I use JavaScript to add the slash characters to the date automatically as the slash character is not available on the numeric virtual keyboard.

Handling errors

It’s important to provide a list of all errors at the top of the form or provide them in-context of the field in error. We can programmatically link the error message with the form field using the aria-describedby attribute. This is only necessary when an error message provides information that helps the user enter the correct data.

...
<input type="email" id="email" name="email" autocomplete="email" aria-required="true" aria-describedby="error-email">
<p id="error-email">Email format email@domain.com</p>
...

When assistive technology users focus on the email field, the error message is announced along with the name, role and state of the field.

Error messages must be visible, persistent and close to the field in error. Don’t rely on color alone to convey errors. Include an icon with “error” as the text. Move focus to the error messages at the top of the form or to the first field in error on form submit.

A form field in error using JavaScript validation

Conclusion

I hope you found something to make your forms more accessible. I like forms because they have such a strong semantic structure. There are a lot of things to get right but it’s pretty straightforward what “right” means in this context: Make sure anything that is conveyed visually is also conveyed to assistive technology, including the states of form fields.

The web is inherently accessible (original)

This is the original version of this post. An updated version from 13 April 2022 is available.

I originally created Accessible Web as a sarcastic reaction to a developer changing his website to require users to disable JavaScript to use it. The reactions on Twitter were hilarious! So many angry developers didn’t get the joke that they do the same thing when they require users to have JavaScript enabled to do anything on their websites.

I decided to create something similar but with CSS. And with a message about semantic HTML. I’ve recreated the experience below.

Accessible Web - please disable CSS to view this website

Whew! That’s better. So guess what?

The web is inherently accessible

Your weekly reminder that the web is accessible by default and it’s our design decisions that stop it being accessible #a11y

k.mar (@Kevmarmol_CT) on Twitter December 7, 2020

Why did you have to disable CSS to view this website? No reason other than a design choice that excludes sighted people.

Did you know? This is how many visitors “view” webpages already:

  • Search engines
  • Bots
  • Site crawlers
  • Analytics
  • Blind people

This webpage is fully accessible to people with screen readers and Braille displays. But many websites are not due to poor design choices that exclude some people.

What is web accessibility?

When a page is accessible, it was developed with the intention of working for as many people with disabilities as possible. A good place to start learning is the W3C’s Introduction to Web Accessibility. Find out the different ways people with disabilities interact with the web.

Web Content Accessibility Guidelines (WCAG)

WCAG is a set of success criteria for determining if a page is accessible, led by four guiding principles:

  • Perceivable: Information and user interface components must be presentable to users in ways they can perceive.
  • Operable: User interface components and navigation must be operable.
  • Understandable: Information and the operation of the user interface must be understandable.
  • Robust: Content must be robust enough that it can be interpreted by a wide variety of user agents, including assistive technologies.

These contain guidelines and a hierarchy of success criteria from Level A to Level AAA. Many accessibility laws, and current best practice, point to WCAG 2.1 Level AA compliance. There are 50 discrete success criteria to evaluate, though many are not applicable to all pages. For example, if a page doesn’t contain video, you don’t have to evaluate against success criteria for captions or audio descriptions.

See the full list of success criteria

Did you know? The WCAG guidelines were first published in 1999. Web accessibility is not a new concept but a lot of people are learning about it only now.

Semantics

So what’s the point? The point is to develop accessible pages from the bottom up, starting with semantic HTML. A whole lot of developers think they know HTML but are actually pretty sloppy about it. Many don’t think it matters if they use a link or a button, but it does. Every semantic mistake introduces accessibility issues into your code. If you’ve never really “learned” HTML, check out this beginner’s guide to writing good HTML.

By far, CSS color contrast issues are the most frequent accessibility issues I see, but the HTML ones are problematic too. Outlined below are the top HTML-related accessibility issues I encounter.

Headings

If you visually scan this page, you can quickly see how it is broken up into sections. That’s due to using headings or the h1-h6 elements. It’s important that every page have at least one h1 so people and search engines know what the topic of the page is. From there, cascade down to h2, h3, and so on.

Did you know? People using screen readers can navigate by headings in much the same way that sighted people can visually scan the page for items of importance.

Form labels

Every input needs a label. It’s really that simple. And best practice is to make that label visible. This helps people remember what information they’ve entered. Programmatically link each pair using the for attribute on the label matching the id attribute on the input.

This enables a couple things:

  • People can now click or tap on the label to give focus to the input. This is especially useful for checkboxes and radio buttons that often have small hit areas.
  • People using screen readers will now hear the label announced when the input is in focus.
<label for="like-cats">
<input type="checkbox" id="like-cats">Please confirm that you like cats</label>

Let people know when a field is required by adding the required attribute to inputs. Many modern browsers do cursory form field validation.

<form action="">
  <label for="cat-email">Cat's Email *</label>
  <input type="email" id="cat-email" required>
  <button>Submit</button>
 </form>

Buttons and links

Buttons and links seem very similar to some developers but they have very different semantic uses. Buttons are used for controls on a page, such as a form submit or toggle, while links are used for navigation, literally for linking to another page. If you use them interchangeably, people can get confused about what a button or link is going to do when activated.

Some people try to make their own custom buttons instead of using the <button> element, which is already accessible:

  • <button> is keyboard focusable
  • <button> is activated with enter and space keys

Tables

There are many legitimate uses for tables on the web but they are often coded incorrectly. Much as an input needs its label, a table data cell needs its table header. This allows the applicable table header to be read by a screen reader before the table data cell contents. Additionally, each table needs a <caption> that provides a description of the table contents to people using assistive technologies

About My Cats
Cat’s nameCat’s color
Lunatorbie
Stellanblack and white

Images

The one thing about accessibility most people know is that images need alt text. This may seem straightforward but let’s look at three examples.

1) Alt text matters

Semantically speaking, every image needs an alt="" attribute. This alone will pass an automated accessibility checker. If an image is purely decorative, you can even leave the alt value empty. But if an image provides context to the content, the alt text must accurately describe the content of the image for people who cannot see it. The alt text for the image below is two cats on an easy chair under a blanket.

two cats on an easy chair under a blanket
2) SVG

SVG doesn’t support the alt attribute. Instead, add a <title> element inside the <svg> element with the alternative text description.

Add the role="img" attribute to the <svg> element to tell assistive technologies that it’s an image.

Add the aria-labelledby attribute to the <svg> element to name the image for assistive technology.

<svg role="img" aria-labelledby="svg-title">
<title id="svg-title">universal access logo</title>
<image
width="300"
height="300"
id="universal-access-logo"
transform="matrix(0.4135 0 0 0.4135 0 0)"
xlink:href="data:image/png;base64,svg-data"></image></svg>
3) Charts and maps

Any time you display complicated information, provide a robust description of the key points of the map or chart. All people will benefit from this for reference. No matter how you mark this up— using <figcaption> or just a <p> element—give that description a unique ID.

Add the aria-describedby="unique-id" attribute to the <img> element. This programmatically connects the image with the description for assistive technologies.

Remember to include the alt attribute with a short explanation of the image. Alt text for the image below is a cat sitting on a bed that is divided up into areas.

a cat sitting on a bed that is divided up into areas
Cat bed, never used. Pillows show wake for food here and purring zone. Other areas on the bed are listed as grooming salon, sleeping area, launch pad to the bedside table, parlor, heaving spot, napping quarters, meditation area, stretching area, yoga studio, and good attack zone. A black cat sits happily in the middle of the bed.

Thanks for reading! Happy accessibility testing.

Colophon

This site was inspired by Heydon’s JavaScript site

You can catch me on Twitter @racheleditullio

Source: Accessible Web About page

Add an accessible honeypot field to your PHP form

A few days ago, after receiving yet another spam submission from the contact form on my portfolio website, I had an epiphany about how to implement a honeypot to block bots instead of a captcha (which nobody likes anyway). I posted about this on Twitter and user @markdeafmcquire asked how I did it.

Captcha versus honeypot

Captchas are those annoying puzzles you have to solve in order to prove you’re human. They are often inaccessible to people with disabilities and they can be difficult to figure out and pass.

Honeypots are passive, often implemented as hidden form fields that bots will fill out and humans won’t. If that hidden form field has a value, then you know the submission is from a bot and can be rejected.

My honeypot solution

My website uses PHP code with HTML to display the contact form as well as doing a server-side check on the form data when submitted.

screen shot of a contact form with fields for name, email, message subject, message and a check box to send a copy to yourself, then a send message button.

The first thing I had to add was a hidden honeypot form field to trip up bots. I used a radio button.

<label for="honeypot" aria-hidden="true" class="visually-hidden">Honeypot <input type="radio" name="honeypot" id="honeypot" style="display:none" value="1"></label>
  • Include the aria-hidden="true" attribute on the <label> element to hide the honeypot from assistive technologies
  • Use a CSS class to hide the <label> element from sighted users
  • Add style="display:none" to the <input> element, which prevents keyboard users from tabbing to the radio button

The second thing I had to add was a check in the PHP to determine if the radio button was selected when the form was submitted. I set the radio button to value="1" and the form check fails if the value of honeypot==1.

//check for honeypot
if ($_POST['honeypot'] == 1) {
    $problem = TRUE;
}

Since it’s only been a few days, I don’t have enough data to determine if my solution is working well. I will update this post in the future.

Update December 2020

This solution has been working well for months now and I have not received any spam. Thanks to @AgenceCreatif on Twitter for trying out the code and offering feedback.

Accessible PHP honeypot form

Below is the PHP and HTML for adding an accessible contact form to your website, including the honeypot, and a way for people contacting you to send a copy of the email to themselves. Just update it with your email address.

See the Pen PHP Form Honeypot by Rachele (@racheleditullio) on CodePen.

Reach out to me on Twitter if you have anything to add.