UPDATE August 2nd 2014: The Australia Post mobile site has been deemed AA compliant with respect to the WCAG accessibility guidelines!
This page is dedicated to documenting some of the lessons learnt while implementing accessibility on iOS devices for the Australia Post mobile site.
Note: the term “everyone” in this article means “sighted users and users of VoiceOver technology on iOS devices”. It is accepted practice - at least in Australia - to focus accessibility testing on iOS devices due to the superior (as of 2014) screen reading technology on those devices, which is known as VoiceOver. Vision Australia endorses this approach because their user-statistics indicate that people that have a need for a mobile screen reader almost exclusively (98%+) use iOS devices, due to the superior experience. On the Android side, there are many screen-reader options with varying degrees of ability and utility.
1. Element Visibility
aria-hiddento hide things from screen readers
- Create a
sr-onlyin Bootstrap lingo) CSS class to hide things from display but make them ‘visible’ to screen readers
style="display:noneto hide things from everyone
2. Setting focus
Moving the focus around is crucial for VoiceOver users to understand what they are “looking” at.
tabindex="0"on non-focussable elements is best.
tabindex="-1"doesn’t always work with VoiceOver - it sometimes hits-then-skips to the next/nearest visible element instead of remaining on the focussed element
- Set focus to the page heading when the page changes. This is especially necessary for single-page applications.
- May need to add descriptive text (e.g.
- Ideally, try to find a visible-to-everyone element that you can set the focus to. Setting focus on elements that are only visible to VoiceOver seems to be unreliable/fragile. (This was the approach I ended up taking when showing search results (see below)).
3. Showing search results
It took a few attempts to get a search-results presentation scheme that worked for everyone. Key points:
- set focus to the first result
- add “result x of y,” message to the beginning of each result item, to provide more context (and avoids the need for a separate “Y results found” label). Note that this string can be hidden from sighted-users.
<span>elements inside an anchor element, in combination with
aria-describedby, for secondary text) allows VoiceOver to read the anchor text smoothly, rather than reading the text-blocks that are inside the anchor one-at-a-time. Using
<div>elements caused the “read-one-text-block-at-a-time” behaviour.
- Adding a
roleattribute to a visually-hidden button element changed the VoiceOver behaviour so that it no longer focussed on the button (when without the
roleattribute, it DID focus it. Roles tried:
heading. I tried this approach initially with a “Y results found” label
aria-controls="searchRegion"to the search textbox created a link to the “searchRegion” element, which would have worked well if it was visible
4. Accessible radio buttons and checkboxes that look nice
- Need certain structure to overlay the input-element on-top-of the label/button to work with Safari
- Use the same name for the collection of inputs, but provide unique ids for each element to allow the label-click to work
- group controls with fieldset
- We wrote an
<abbr>directive to make abbreviations accessible by converting the
<abbr title="...">...</abbr>text into a visually-hidden child node of the
<abbr>element, then using
aria-hiddenon the abbreviation itself. This doesn’t work when the abbreviation is inside an block that you are using via
aria-describedby, which just reads the text (ignoring the aria-hidden attributes in most cases) or reverts to reading in block mode instead of reading the text like a sentence.
Not too hard to implement, though there are still some unexpected (to me at least) behaviours when opening/closing tab-accordians.
Basically, you have a parent element with
role="tablist" to contain the tab, each tab has
role="tab", tab panels have
You can use
aria-labelledby to label the tabs some content in the corresponding tab panel.
Same as standard tabs: parent element has
role="tablist", each tab has
role="tab", tab panels have
Additionally, add an
aria-expanded="true/false" to each tab, backed up with some visually hidden text to match (e.g. “expanded”, “collapsed”)
When the tab state changes (and hence, the text inside the tab element (or the
aria-selected value) changes), Voiceover reads the OLD state THEN the new state.
Before the tab panel is opened, Voiceover says “collapsed,
- If using
ng-checkedproperty, you need to be careful to ensure that
ng-modelexpression in it’s own expression. I had a case where the
ng-modelexpression was not part of the
ng-checkedexpression, and when the checkbox changed from checked to unchecked (with the
true), visually the checkbox looked checked, but clicking on it did not change the
ngModelvalue (it was still
true). It required a second click to change the checked state. E.g.
ng-model="a" ng-checked="a || b || c" // Include "a" in the expression