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
<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
<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 thefor
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
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
<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)"
<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 nowaria-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
<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)