Dynamic accessible descriptions reference
I recently needed to test the support of a dynamic accessible description – a element’s description that is initially one (or no) value, then changes to another value after an action takes place.
I created a page of examples to test and a document to record my findings, but then realized that I could just make this a blog post that I periodically update. That way, others can use it as a reference as well, if they choose.
Testing scope #
I tested four common desktop and two mobile browser and screen reader combinations. All screen readers were using their default settings.
Desktop #
- JAWS (2023.2306.38) + Chrome (120) on Windows 10
- NVDA (2023.3.0.29780) + Firefox (120) on Windows 10
- Narrator (Windows 10 v22H2) + Edge (120) on Windows 10
- VoiceOver + Safari (17.2.1) on MacOS 13.6.3
Note:
A note on MacOS’s VoiceOver: recently the VoiceOver and WebKit teams changed the way that accessible descriptions are handled.
When MacOS VoiceOver encounters a description, an item’s announcement is followed by “more content available” and instructions to use the shortcut CONTROL + OPTION + COMMAND + / (slash).
If the shortcut is used, a “more content” menu appears. You can use the arrow keys to navigate a list of additional content, which in this case is the item’s accessible description(s).
This is a laborious process that I’ve seen many call unnecessary and annoying. But at least for now that’s how it is. Please keep this process in mind when looking at the MacOS VoiceOver results.
Mobile #
- TalkBack (14.0.1) + Chrome (119) on Android 11, Samsung Galaxy A20s
- VoiceOver + Safari on iOS 17.3, iPhone 13 Pro
Note:
Like MacOS, iOS VoiceOver also has a quirk when it comes to accessible descriptions: when iOS encounters one, it adds the word “description” before announcing that content. Again, many consider this needless verbosity.
Test elements #
I tested changing the descriptions of two elements: a button and a text input. This isn’t inclusive of every element you could add an accessible description to, but they’re both very common use cases.
Test elements markup
html<button>Apple</button> <input type="text" aria-label="Apple" value="Fuji">
button element #
The button contains the text “apple”. Activating the button triggers its description to change.
Text input #
The text input has an accessible name of “apple” (via the aria-label property) and contains the text “fuji”.
If the enter key is pressed while focused, the input's accessible description is changed.
Testing actions #
Most elements were tested in five ways:
- When navigated to with the virtual cursor before the change
- When navigated to with the virtual cursor after the change
- When navigated to with the tab key before the change
- When navigated to with the tab key after the change
- The announcement made while focused when the change occurs
The virtual cursor and the tab key are not the only ways that screen reader users navigate, but they give a reasonable approximation of support that can be supplemented with further testing.
Mobile testing notes #
Please note that the mobile screen readers were only tested with their virtual cursor, so TalkBack and iOS VoiceOver won’t have results in the tab key navigation columns.
Furthermore, there’s no easy way to trigger the enter key event of the input with the mobile devices’ virtual keyboards, so the change announcement column won’t have results in the text input for those screen readers.
Results #
Here are the approaches I tested and how they performed.
Approach #1: adding a new aria-describedby attribute with value #
For this approach, the elements initially have no accessible description.
After being activated, the aria-describedby attribute is added with a value matching the id of a third element. This should give the elements an accessible description of “fruit”.
Approach #1 markup
html<!-- Approach #1: Adding new 'aria-describedby' attribute with value --> <!-- before --> <button>Apple</button> <input type="text" value="Fuji" aria-label="Apple"> <!-- after --> <button aria-describedby="desc1">Apple</button> <input type="text" value="Fuji" aria-label="Apple" aria-describedby="desc1"> <p id="desc1">Fruit</p>
| Browser / screen reader | button before change, via virtual cursor |
button after change, via virtual cursor |
button before change via tab |
button after change via tab |
button on change |
|---|---|---|---|---|---|
| JAWS + Chrome | "apple button" | "apple button" | "apple button" | "apple button fruit" | "fruit" |
| NVDA + Firefox | "button apple" | "button apple" | "apple button" | "apple button fruit" | "fruit" |
| Narrator + Edge | "apple button" | "apple button fruit" | "apple button" | "apple button fruit" | [no announcement] |
| VoiceOver + Safari | "apple button" | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | "apple button" | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | [no announcement] |
| TalkBack + Android Chrome | "apple button" | "apple button fruit" | N/A | N/A | [no announcement] |
| VoiceOver + iOS Safari | "apple button" | "apple button" | N/A | N/A | "apple" |
| Browser / screen reader | input before change, via virtual cursor |
input after change, via virtual cursor |
input before change via tab |
input after change via tab |
input on change |
|---|---|---|---|---|---|
| JAWS + Chrome | "apple edit fuji" | "apple edit fuji" | "apple edit fuji type text" | "apple edit fuji fruit type text" | "fruit" |
| NVDA + Firefox | "apple edit fuji" | "apple edit fuji" | "apple edit selected fuji" | "apple edit fruit selected fuji" | "fruit" |
| Narrator + Edge | "apple edit fuji" | "apple edit fuji fruit" | "apple edit fuji" | "apple edit fuji fruit" | [no announcement] |
| VoiceOver + Safari | "fuji contents selected apple edit text" | “fuji contents selected apple edit text [pause] more content available [shortcut] [down arrow] fruit description” | "fuji contents selected apple edit text" | “fuji contents selected apple edit text [pause] more content available [shortcut] [down arrow] fruit description” | [no announcement] |
| TalkBack + Android Chrome | "fuji edit box apple" | "fuji edit box apple fruit" | N/A | N/A | N/A |
| VoiceOver + iOS Safari | "apple fuji text field" | "apple description fruit fuji text field" | N/A | N/A | N/A |
Approach #2: changing the text content of element already linked via the aria-describedby attribute #
This approach gives the elements an aria-describedby attribute from the start, which sets an accessible description of “fruit”.
After being activated, the text content of desc2 changes, making the new description “vegetable”.
Approach #2 markup
html<!-- Approach #2: Changing the text content of element --> <!-- already linked via the 'aria-describedby' attribute --> <button aria-describedby="desc2">Apple</button> <input type="text" value="Fuji" aria-label="Apple" aria-describedby="desc2"> <!-- before --> <p id="desc2">Fruit</p> <!-- after --> <p id="desc2">Vegetable</p>
| Browser / screen reader | button before change, via virtual cursor |
button after change, via virtual cursor |
button before change via tab |
button after change via tab |
button on change |
|---|---|---|---|---|---|
| JAWS + Chrome | "apple button" | "apple button" | "apple button fruit" | "apple button vegetable" | "vegetable" |
| NVDA + Firefox | "button apple" | "button apple" | "apple button fruit" | "apple button vegetable" | "vegetable" |
| Narrator + Edge | "apple button fruit" | "apple button vegetable" | "apple button fruit" | "apple button vegetable" | [no announcement] |
| VoiceOver + Safari | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | [no announcement] |
| TalkBack + Android Chrome | "apple button fruit" | "apple button vegetable" | N/A | N/A | [no announcement] |
| VoiceOver + iOS Safari | "apple description fruit button" | "apple description fruit button" | N/A | N/A | "apple description fruit" |
| Browser / screen reader | input before change, via virtual cursor |
input after change, via virtual cursor |
input before change via tab |
input after change via tab |
input on change |
|---|---|---|---|---|---|
| JAWS + Chrome | "apple edit fuji" | "apple edit fuji" | "apple edit fuji fruit type text" | "apple edit fuji vegetable type text" | "vegetable" |
| NVDA + Firefox | "apple edit fuji" | "apple edit fuji" | "apple edit fruit selected fuji" | "apple edit vegetable selected fuji" | [no announcement] |
| Narrator + Edge | "apple edit fuji fruit" | "apple edit fuji vegetable" | "apple edit fuji fruit" | "apple edit fuji vegetable" | [no announcement] |
| VoiceOver + Safari | “fuji contents selected apple edit text [pause] more content available [shortcut] [down arrow] fruit description” | “fuji contents selected apple edit text [pause] more content available [shortcut] [down arrow] fruit description” | “fuji contents selected apple edit text [pause] more content available [shortcut] [down arrow] fruit description” | “fuji contents selected apple edit text [pause] more content available [shortcut] [down arrow] fruit description” | [no announcement] |
| TalkBack + Android Chrome | "fuji edit box apple fruit" | "fuji edit box apple vegetable" | N/A | N/A | N/A |
| VoiceOver + iOS Safari | "apple description fruit fuji text field" | "apple description vegetable fuji text field" | N/A | N/A | N/A |
Approach #3: adding a new aria-description attribute with value #
Here, the elements begin with no accessible description.
Upon activation, the aria-description attribute with a value of “fruit” is added, making that the new accessible description.
Approach #3 markup
html<!-- Approach #3: Adding new 'aria-description' attribute with value --> <!-- before --> <button>Apple</button> <input type="text" value="Fuji" aria-label="Apple"> <!-- after --> <button aria-description="fruit">Apple</button> <input type="text" value="Fuji" aria-label="Apple" aria-description="fruit">
| Browser / screen reader | button before change, via virtual cursor |
button after change, via virtual cursor |
button before change via tab |
button after change via tab |
button on change |
|---|---|---|---|---|---|
| JAWS + Chrome | "apple button" | "apple button" | "apple button" | "apple button fruit" | "fruit" |
| NVDA + Firefox | "button apple" | "button fruit apple" | "apple button" | "apple button fruit" | "fruit" |
| Narrator + Edge | "apple button" | "apple button fruit" | "apple button" | "apple button fruit" | [no announcement] |
| VoiceOver + Safari | "apple button" | "apple button" | "apple button" | "apple button" | [no announcement] |
| TalkBack + Android Chrome | "apple button" | "apple button fruit" | N/A | N/A | [no announcement] |
| VoiceOver + iOS Safari | "apple button" | "apple button" | N/A | N/A | "apple" |
| Browser / screen reader | input before change, via virtual cursor |
input after change, via virtual cursor |
input before change via tab |
input after change via tab |
input on change |
|---|---|---|---|---|---|
| JAWS + Chrome | "apple edit fuji" | "apple edit fuji" | "apple edit fuji type text" | "apple edit fuji fruit type text" | "fruit" |
| NVDA + Firefox | "apple edit fuji" | "apple edit fruit fuji" | "apple edit selected fuji" | "apple edit fruit selected fuji" | "fruit" |
| Narrator + Edge | "apple edit fuji" | "apple edit fuji fruit" | "apple edit fuji" | "apple edit fuji fruit" | [no announcement] |
| VoiceOver + Safari | "fuji contents selected apple edit text" | "fuji contents selected apple edit text" | “fuji contents selected apple edit text” | “fuji contents selected apple edit text” | [no announcement] |
| TalkBack + Android Chrome | "fuji edit box apple" | "fuji edit box apple fruit" | N/A | N/A | N/A |
| VoiceOver + iOS Safari | "apple fuji text field" | "apple description fruit fuji text field" | N/A | N/A | N/A |
Approach #4: changing value of existing aria-description attribute #
This last approach sees the elements begin with the aria-description value of “fruit”.
After being activated, that value changes to “vegetable”, making that the new accessible description.
Approach #4 markup
html<!-- Approach #4: Changing value of existing 'aria-description' attribute --> <!-- before --> <button aria-description="fruit">Apple</button> <input type="text" value="Fuji" aria-label="Apple" aria-description="fruit"> <!-- after --> <button aria-description="vegetable">Apple</button> <input type="text" value="Fuji" aria-label="Apple" aria-description="vegetable">
| Browser / screen reader | button before change, via virtual cursor |
button after change, via virtual cursor |
button before change via tab |
button after change via tab |
button on change |
|---|---|---|---|---|---|
| JAWS + Chrome | "apple button" | "apple button" | "apple button fruit" | "apple button vegetable" | "vegetable" |
| NVDA + Firefox | "button fruit apple" | "button vegetable apple" | "apple button fruit" | "apple button vegetable" | "vegetable" |
| Narrator + Edge | "apple button fruit" | "apple button vegetable" | "apple button fruit" | "apple button vegetable" | [no announcement] |
| VoiceOver + Safari | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | “apple fruit button [pause] more content available [shortcut] [down arrow] fruit description” | [no announcement] |
| TalkBack + Android Chrome | "apple button fruit" | "apple button vegetable" | N/A | N/A | [no announcement] |
| VoiceOver + iOS Safari | "apple description fruit button" | "apple description fruit button" | N/A | N/A | "apple description fruit" |
| Browser / screen reader | input before change, via virtual cursor |
input after change, via virtual cursor |
input before change via tab |
input after change via tab |
input on change |
|---|---|---|---|---|---|
| JAWS + Chrome | "apple edit fuji" | "apple edit fuji" | "apple edit fuji fruit type text" | "apple edit fuji vegetable type text" | "vegetable" |
| NVDA + Firefox | "apple edit fruit fuji" | "apple edit vegetable fuji" | "apple edit fruit selected fuji" | "apple edit vegetable selected fuji" | "vegetable" |
| Narrator + Edge | "apple edit fuji fruit" | "apple edit fuji vegetable" | "apple edit fuji fruit" | "apple edit fuji vegetable" | [no announcement] |
| VoiceOver + Safari | “fuji contents selected edit text [pause] more content available [shortcut] [down arrow] fruit description” | “fuji contents selected edit text [pause] more content available [shortcut] [down arrow] fruit description” | “fuji contents selected edit text [pause] more content available [shortcut] [down arrow] fruit description” | “fuji contents selected edit text [pause] more content available [shortcut] [down arrow] fruit description” | [no announcement] |
| TalkBack + Android Chrome | "fuji edit box apple fruit" | "fuji edit box apple vegetable" | N/A | N/A | N/A |
| VoiceOver + iOS Safari | "apple description fruit fuji text field" | "apple description vegetable fuji text field" | N/A | N/A | N/A |
Key takeaways #
At this time, there does not appear to be one approach without some manner of support holes, which is unfortunate. With that in mind, here’s a few things that I noticed.
Don’t rely on accessible descriptions for dynamic information #
From looking at the results tables for the approaches I tested, there was no one method that worked across the board for relaying a dynamic accessible description.
So if you have dynamic information that absolutely must be relayed to screen reader users – like an error message, instructions, or a state value – you can’t only rely on placing that information in an element’s accessible description without leaving out some portion of your users.
Navigation methods are not equal #
Navigating via the tab key tended to result in the accessible description being announced at a much higher rate than navigating via the virtual cursor.
This was the most apparent with JAWS and NVDA. JAWS never once announced an accessible description via the virtual cursor, while NVDA did not with the aria-describedby attribute but did with aria-description.
Text inputs have broader description support #
The text inputs tended to have their accessible descriptions (and changes to them) announced more frequently than the button elements.
This was the most apparent with VoiceOver. Both the MacOS and iOS flavors of it had trouble recognizing that a button description had changed (or even had one to begin with), whereas it did not always have that trouble with the text input.
VoiceOver has big issues with accessible descriptions #
Both MacOS and iOS versions of VoiceOver were inconsistent with the handling and support of accessible descriptions.
MacOS VoiceOver seemed to do ok only recognizing when a new aria-describedby attribute is added to an element when not present initially, but in any other scenario it doesn’t recognize any changes to a description.
iOS VoiceOver will not recognize any changes to a button description whatsoever, either if there’s none present on page load and then later added, or if there is one present initially that later changes. The support is a lot better for text input elements, though.
This is on top of the weird accessible description behavior presently incorporated into VoiceOver. MacOS has the cumbersome “more content available” workflow, while iOS adds the word “description” when it does announce descriptions.
If and when VoiceOver improves its support of accessible descriptions, it will plug a lot of holes that currently exist.
Changes to accessible descriptions of focused elements are not reliably announced #
Outside of JAWS and NVDA, which both do this well, the remaining screen readers all either announced nothing, or something incorrect at the moment an element's accessible description changed.
If your interface needs users to be alerted to changes in a focused element's description, you'd need to consider a live region or other technique.
Test page #
If you’d like to visit the test page yourself, I hosted it on CodePen:
Conclusion #
As mentioned, I will try to periodically update this page as browsers and screen readers receive updates. Hopefully, the support gaps here will be fixed at some point, giving accessible descriptions wide and reliable support.
Questions or comments? Feel free to email me or ping me on Mastodon.