Home
A11y Tech Talk
Forms

Accessible Forms

  • a form is an element that allows users to provide data
  • can be as simple as a single field or as complicated as a multi-step form element with multiple fields, input validations or even CAPTCHA
  • one of the most difficult elements to get right from an accessibility perspective, due to usage of many different elements with their respective rules plus form specific accessibility requirements

Why do we need accessible forms

  • for screenreader users, users that use the keyboard and everybody who likes clear instructions and a quick process

Use native HTML

  • whenever possible
  • most of the native HTML form controls are accessible by default
  • easier to maintain / to understand

ARIA vs. HTML

    <div role="form" id="aria">
       <!-- form content -->
    </div>
    <form id="html">
      <!-- form content -->
    </form>

Forms structure

  • form structure is important to maintain the power of native HTML forms
  • the following question for extra bacon LOOKS exactly the same as the one with the cheese
  • but navigating with the keyboard and a SR: it would only read the answers with no relation to the question

Would you like some extra bacon?


<form>
  <p>Would you like some extra bacon?</p>
 
   <input type="radio" id="yesBacon" name="bacon" value="yesBacon" />
   <label for="yesBacon">Yes, please!</label>
 
   <input type="radio" id="noBacon" name="bacon" value="noBacon" />
   <label for="noBacon">No, thanks!</label>
</form>
  • that is why:
  • a <form> should contain one or multiple <fieldset> elements, which group several controls as well as label
  • within the fieldset is the <legend> element to provide a caption
  • this structure forms a relation between question and answer

Would you like some extra cheese?
<form action="#">
  <fieldset>
  <legend>Would you like some extra cheese?</legend>
    <input type="radio" id="yesCheese" name="agree" />
    <label for="yesCheese">Yes, please!</label>
 
    <input type="radio" id="noCheese" name="disagree" />
    <label for="noCheese">No, thanks!</label>
  </fieldset>
</form>

Attention: don't forget the <form> element, especially when using onclick events or similar. Whenever form controls need to be submittable, they should be placed within a <form> tag.

<label>

  • <label> defines label for several elements, like <input>, <select> and <textarea>
  • screenreaders will read out loud the label when element is focused
  • labels should be distinctive and descriptive

Bad:



Good:




  • for each input field there should be a label using the for attribute
<label for="txtFirstName">First Name</label>
<input type="text" id="txtFirstName" />
  • there is also the possibility to use the label element around the input without the need for the for attribute
<label>
  First Name
  <input type="text" />
</label>
  • but if there are non-interactive elements, they can easily be missed (like a paragraph e.g. terms and conditions)
  • they need to be attached to the form controls specifically
  • SR interact with form controls using focus mode, meaning they navigate back and forth between focusable elements using the Tab key

aria-describedby

  • to make those non-interactive elements visible to SR you can use aria-describedby
  • this attribute is useful to provide supplemental information about an control element, e.g. a date format, max length of an input field, password requirements
  • good for users skimming through a form, so they only get the information when they actually navigate to the field
  • texts with aria-describedby can be hidden (hidden="hidden"), but don't have to be

  • I entered all data truthfully

  • I accept the terms and conditions

  • Of course, I would like to receive a Newsletter!

Attention: Some SR will read the description but only with a long pause. This could lead to users accidentally skipping the description and never hearing it.

tabindex="0"

  • it may seem like a good idea to prevent non-focusable content (heading, important paragraph or list) from being skipped by making it focusable
  • tabindex="0" for giving an element focus and tabindex="-1" to revoke it
  • but only elements that provide some interaction (button, links, checkboxes..) should be focusable
  • users may be confused why a text is focused and expect some sort of interaction with it
  • this also is counterproductive, since you would've already finished interacting with the control element (the "agree" checkbox in this example) and would receive the information later than necessary

aria-label

  • use mostly for custom form elements
  • they are well-supported for interactive elements but not well-supported for statix text elements
  • if you're reaching to aria-label often, it may be a sign to reconsider the semantic markup

<dialog>

  • native HTML element serves e.g. as a modal, dismissible alert or subwindow
  • needs autofocus attribute because user is expected to interact with it immediately
  • if no other element (like an input field) is implemented, autofocus should be on the close button or dialog itself

This modal dialog has a groovy backdrop!

<dialog>
  <button class="forms__button" autoFocus>Close</button>
  <p>This modal dialog has a groovy backdrop!</p>
</dialog>
<button class="forms__button">Show the dialog</button>

Attention! Do not add tabindex property to <dialog> since the dialog and corresponding close button can already receive focus and be interactive

Irritating screen reader behaviours

  • in general they handle forms pretty uniformly
  • some behaviours occur that may be surprising, if not confusing and some are simply buggy
  • this is mainly referred to NVDA but JAWS also has some issues

Announcing of controls' attributes

  • HTML offers many attribues to customize behaviour of form controls
<input type="text" />
<input type="checkbox" />
  • NVDA announces the "edit" (for text) of "checkbox" so the user knows how to deal with the input field

Some attributes are non-intuitive

  • plain text inputs are announced as "has autocomplete" by NVDA
  • because HTML offers and autocomplete attribute for single line input; and its default is set to true
  • basically shows previously typed inputs by the user
  • this may be counter-intuitive because of known autosuggest feature like google has it

Test: https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_autocomplete2 (opens in a new tab)

Solution: autocomplete="off" or explain what the feature does with aria-describedby

Missing announcements

  • some attributes seem to not be announced at all like maxlength option on <input type="text"/>

  • not even when the user's input reaches the maximum input length

  • not announced by NVDA; since July 2024 available with JAWS

  • multiple option of <select> element is not announced by NVDA; JAWS announces it as "multi select"

Solution: Display information inside the label for example "Full name (max. 20 character)"

Company Survey
<form id="best-company">
  <fieldset>
  <legend>Company Survey</legend>
    <label for="bestCompany">What company is the best?</label>
    <select name="bestCompany" id="bestCompany">
      <option value="dotSource GmbH">dotSource GmbH</option>
      <option value="dotSource SE">dotSource SE</option>
      <option value="dS">dS</option>
      <option value="all of the above">all of the above</option>
    </select>
  </fieldset>
</form>

Disabled Buttons

  • buttons are disabled for indicating, that input is invalid or incomplete
  • disable a button is valid standard technique but confusing for screenreaders since the button is not focusable
  • SR users will not recognise the end of the form
<button className="forms__button" type="submit" disabled>Disabled</button>

Solution: use aria-disabled="true" to allow the button to receive focus and announcing its disabled state

<button className="forms__button" type="submit" aria-disabled="true">Disabled with Aria</button>

How to handle form errors

Required field

  • the common red * is the most common visual indicator for a required field
  • SR will it only read as a word like "star" or "asterisk" or completely ignore it

(possible) Solution: Hide the * for SR with aria-hidden="true". Add the required attribute to the element.

<label for="address" class="field-label">
  Address <span class="field-star" aria-hidden="true">*</span>
</label>
<input id="address" type="text" required />

-> Name field in form example

  • sadly this standard error message is often not wanted, because its not styleable
  • therefore a custom error message with aria-required="true" is needed
  • this adds semantics (SR says it is required) but it does not:
    • prevent invalid submit
    • show custom error message
    • auto-focus invalid field

Colored error messages

  • its tempting to only use a red color to signal errors
  • people with color blindness or different perceptions of color won't regonise this as an error

Solution: complement the color with an icon, shadow and a message whats missing

The Message

  • the error message needs to be short and precise and tells the user how to fix it
  • to mark the field as invalid use aria-invalid="true" to make the SR announce its invalid
  • then use aria-describedby to point to the error element
  • in the following example you can see how aria-describedby accepts multiple IDs which allows for multiple announcements in the given order

Example: https://codepen.io/sandrina-p/pen/oNMEBXg (opens in a new tab)

  • its also good practice to display all errors above the form with focus on them so that the SR announces them

  • those messages have clear instructions that are programmatically connected to the input field

  • there already exists a aria-errormessage attribute but its not yet supported by most SR, so for now aria-describedby is the best choice

Attention: If the information in the aria-describedby changes e.g. updates a status, rather use aria-live, because aria-describedby is static

CAPTCHA

  • if a form uses CAPTCHA it is not accessible; to make it accessible it needs to be removed
  • there are other ways to make a form secure:
    • have a hidden field to catch robots; if the field is completed/filled out the form won't submit
    • require users to confirm their email address when signing up for a newsletter

Form example

How to order pizza online (fast)

Customer Info


Size


Toppings





Would you like some extra cheese?


<form id="complete-forms-example">
  <h2>How to order pizza online (fast)</h2>
  <fieldset>
    <legend>Customer Info</legend>
    <label>Name: <input type="name" required aria-required="true"/></label>
    <label>Telephone: <input type="tel" aria-required="true"/></label>
    <label>Email address: <input type="email"/></label>
  </fieldset>
 
  <fieldset>
    <legend>Size</legend>
    <label><input type="radio" name="size" value="20cm"/>20cm</label>
    <label><input type="radio" name="size" value="24cm"/>24cm</label>
    <label><input type="radio" name="size" value="28cm"/>28cm</label>
  </fieldset>
 
  <fieldset>
    <legend>Toppings</legend>
    <label><input type="checkbox" name="topping" value="mushrooms"/>Mushrooms</label>
    <label><input type="checkbox" name="topping" value="bacon"/>Bacon</label>
    <label><input type="checkbox" name="topping" value="onions"/>Onions</label>
    <label><input type="checkbox" name="topping" value="ham"/>Ham</label>
    <label><input type="checkbox" name="topping" value="tuna"/>Tuna</label>
    <label><input type="checkbox" name="topping" value="veggies"/>Veggies</label>
 
  </fieldset>
  <fieldset>
  <legend>Would you like some extra cheese?</legend>
    <input type="radio" id="yesCheese" name="cheese" value="yesCheese" />
    <label for="yesCheese">Yes, please!</label>
 
    <input type="radio" id="noCheese" name="cheese" value="noCheese" />
    <label for="noCheese">No, thanks!</label>
  </fieldset>
  <label>Special requests: </label>
 
  <textarea aria-describedby="delivery-information" name="comments"></textarea>
  <p id="delivery-information" hidden="hidden">Extra toppings</p>
  <button class="forms__button">Submit order</button>
</form>

Custom form elements

  • what if the native HTML elements aren't enough?
  • use ARIA roles to simulate native HTML

Fake accessible link to a complete ARIA roles list created using a span

<span data-href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles" tabIndex="0" role="link">
  Fake accessible link to a complete ARIA roles list created using a span
</span>

Possible topics for future Techtalks

  • custom form elements

Sources

https://www.accessibility-developer-guide.com (opens in a new tab) https://developer.mozilla.org/en-US/docs/Web/HTML/Element (opens in a new tab) https://www.smashingmagazine.com/2023/02/guide-accessible-form-validation/ (opens in a new tab) https://www.w3schools.com/accessibility/accessibility_forms_intro.php (opens in a new tab) https://web.dev/learn/accessibility/forms (opens in a new tab)