Background
The purpose of this document is to provide a list of accessibility (or a11y for short) issues that engineers should be cognizant of when developing. A developer can support building accessible websites, often by adhering to the HTML standards; but there are some a11y issues that are not as intuitive or unusual. By providing this lessons learned from a11y documentation, hopefully other engineers will be able to recognize and catch issues early on the development process when they are either writing code, reviewing a PR or dev QAing.
What is VoiceOver?
VoiceOver is the built-in screen reader in Apple OS X and iOS. Its purpose is to provide increased accessibility to blind or low-vision users, as well as users with dyslexia. A a11y issues can stem from elements or sections of the page that the screen reader does not provide enough or sometimes too much information to the user.
Getting Started with VoiceOver
Description | Keyboard Shortcut |
---|---|
VO Key | control + option |
Turn VoiceOver on/off | ⌘ + F5 |
Move VoiceOver cursor to next item | VO + Right Arrow |
Move VoiceOver cursor to previous item | VO + Left Arrow |
Click/push a selected item | VO + Space bar |
Interact with an item (drill down) | VO + Shift + Down Arrow |
Stop interacting with an item (drill up) | VO + Shift + Up Arrow |
Use Auto Web Spots | VO + ⌘ + N |
Open the Rotor | VO + U |
Access VoiceOver settings | VO + ⌘ + Left or Right Arrows |
Change VoiceOver setting | VO + ⌘ + Up or Down Arrows |
Access the Dock | VO + D |
VoiceModifier key (hereafter referred to as VO) | CTRL + OPTION or CAPS |
Lessons Learned from a11y
Ensure error message/widget are associated to related inputs
Ensure error messages are associated with related inputs: When a user exits an input and error message displays then the screen reader should notify the user of the error.
Recommendation:
The error message should be associated with its related input with aria-describedby
referencing a unique id
on the message element.
Example:
<input
id="my-input"
type="text"
aria-label="Enter some value"
placeholder="Enter some value"
value=""
aria-describedby="unique-error-widget-id"
/>
<div
id="unique-error-widget-id"
className="error"
data-testid="errorWidget"
>
Error, you have entered an invalid input.
</div>
Ensure the user is informed of dynamically updating content
With SPA apps, a user action of clicking or inputting data can dynamically change sections of the page. While this visual changes may be apparent to users who can see the page, they are not as obvious to users of assistive technologies. This is where ARIA live attributes help fill this gap and provide a way to programmatically expose dynamic content changes in a way that can be announced by assistive technologies.
For example, if a user inputs a value and the updated content occurs the point of current programmatic focus then we can use ARIA live attributes to notify the user.
The aria-live
attribute notify the user if the region receives an update. There are three possible values to pass to aria-live
: off
, polite
, or assertive
. The default is off
. Normally you should use polite
. If a region has a label or text accompanied by the dynamic content then you can use aria-atomic
so that the screen reader will read the entirety of that section.
Example: CodeSandBox
SVG and Images
If an image provides valuable content to the experience then the <img/>
tag should include a concise description of the image’s purpose in the alt
attribute. Else if the image does not provide any meaningful information to the content of the page then you can provide a null alt text (e.g. alt=""
) to the image.
If you have inline SVGs the same concept should be applied as above. If the SVG does provide valuable content to the experience then add a <title/>
element in the SVG and the title should be referenced by an aria-labelledby
attribute of the svg
element. Else if the SVG does not provide any meaningful information then add a aria-hidden="true"
to the SVG element.
SVG Example:
<!-- If an SVG provides valuable content: -->
<svg aria-labelledby="my-svg-title">
<title id="my-svg-title">My Creative Title</title>
<!-- ... -->
</svg>
<!-- If an SVG does not provides valuable content: -->
<svg aria-hidden="true">
<!-- ... -->
</svg>
Ensure content structure is in a linear progression
When positioning elements with text content on page it is important that the structure of the elements are in semantic order: meaning they should read in a logical sequence. For example, when displaying a label with a value they should be composed in readable sequence where the label should be directly followed by its value or vice versa. Otherwise this will confuse screen reader users if the content is read in mish-mash order. Let’s first take example of what you should not do:
<!-- ❌ WRONG ❌ -->
<div class="labels">
<p class="your-progress-text">
Your Progress:
</p>
<p class="next-checkpoint-text">
Next Checkpoint:
</p>
</div>
<div class="stat-values">
<p class="your-progress-distance">
6.12 miles
</p>
<p class="next-checkpoint-distance">
10 miles
</p>
</div>
The above example will be announced by screen reader as: “Your Progress: Next Checkpoint: 6.12 miles 10 miles.” This is not the linear flow where it will confuse screen reader users. Instead it should be structured like so:
<!-- ✅ CORRECT ✅ -->
<div>
<p class="your-progress-text">
Your Progress:
</p>
<p class="next-checkpoint-text">
6.12 miles
</p>
</div>
<div>
<p>
Next Checkpoint:
</p>
<p>
10 miles
</p>
</div>
Now it will be read as: “Your Progress: 6.12 miles Next Checkpoint: 10 miles.”
Summary
Sight users have the ability to scan the screen almost instantaneously, allowing them to quickly comprehend the layout and design of the page content. While screen reader users cannot discern this level of detail. So it is important that the structure of you code is in linear progression where it can be read from beginning to end.
Progress Bar VO
Issue: When using role="progressbar"
to create a progress bar there are discrepancies between browsers, OS, and AT/screen reader software. See this issue here of some of the problems that one may incur with VO of different screen readers, browsers, and OS. These variations are likely due to bugs of the screen reader software and/or browsers.
According to W3C recommendation here for progress bars:
The author SHOULD supply values for
aria-valuenow
,aria-valuemin
, andaria-valuemax
, unless the value is indeterminate, in which case the author SHOULD omit thearia-valuenow
attribute.
and
Assistive technologies generally will render the value of
aria-valuenow
as a percent of a range between the value ofaria-valuemin
andaria-valuemax
, unlessaria-valuetext
is specified. It is best to set the values foraria-valuemin
,aria-valuemax
, andaria-valuenow
in a manner that is appropriate for this calculation.
With a crude fix of trying to avoid issues outlined here, we can implement the following, trying to follow the well-documented patterns from MDN web docs here and W3C recommendation here. But there still may be some inconsistency and duplication of text being read depending on the AT/screen reader and browser.
<div
className="progress"
>
<div
className="progress-bar"
aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={round(value, 2)}
aria-valuetext={`${round(value, 2)}% Complete`}
role="progressbar"
aria-label="Progress bar"
/>
</div>
Resources:
- https://github.com/twbs/bootstrap/issues/16801
- https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_progressbar_role
- https://www.w3.org/TR/wai-aria-1.1/#progressbar
- https://dequeuniversity.com/rules/axe/4.1/aria-progressbar-name