Main Menu

Breadcrumbs

(Not so) Simple ARIA Tree Views and Screen Readers

I started testing a number of screen readers with different ARIA tree views. It turns out there's a bit going on with screen readers and tree views, so the research got a little lengthy. It also turns out that there's significant variability across screen readers in how they handle different ARIA tree views. I found no single way to build a simple tree view that provides what I'd call a fully decent experience in current, popular screen readers.

I have, however, drawn on the test results to develop an approach that I think provides the best support for current screen reader and browser combinations, albeit with some compromises. That best approach is described below. The example tree views and test results come after, should you be interested.

Also note that I'm not a real JavaScript or jQuery developer by any stretch, so while the scripting behind the tree view examples does work, its construction is certainly kinda naff. That is to say, feel free to repurpose it to your own end, but your mileage may vary.

What's a Tree View?

A tree view is a component to navigate hierarchical lists. It is made up of one or more top level nodes. A node may have children or it may be an end node. Nodes with children can be expanded or collapsed - when expanded its child nodes are visible. When collapsed the children are not visible. There is generally some sort of visual indication whether a node has children and can be expanded. Any number of nodes can be expanded at a time and child nodes may contain children.

This type of navigational component should be familiar. It's available in some form or another for viewing and navigating the folder and file hierarchies in major operating systems. It is also used in web applications that include file or document management functionality.

While they are very common, tree views are not the simplest of interactive widgets, and ensuring they are accessible to screen readers users means providing good keyboard and focus support, as well as relaying information about the structure and state of the tree. Fortunately, ARIA provides a number of roles and properties that can be used to accomplish this.

Right arrow expands a closed node, moves to the first child of an open node, or does nothing on an end node.

In the standard tree view, only one node should be in the Tab order and focussable at any one time. This is typically done by managing the tabindex attributes on the tree items. A value of "-1" removes an element from the Tab order, while a value of "0" adds it (if it isn't natively focussable). As such, only the currently focussed tree node should ever have tabindex="0". All the others should have tabindex="-1". When focus moves to a different node, the tabindex values are updated accordingly. We can refer to this as the roaming tabindex approach since tabindex="0" roams around the tree view with the focussed node.

Another approach, and that used in the best approach proposed here, involves maintaining focus on the tree container itself. Every node is kept out of the Tab order. To keep track of which node is active, aria-activedescendant is set on the tree container and dynamically updated with a reference to the id of the currently focussed node.

Expected Ideal Screen Reader Behaviour

Different screen readers in different browsers show a range of behaviours with ARIA tree views. If we consider all those behaviours together with the programmatic roles and states that ARIA provides, I would suggest that ideal screen reader behaviour with tree views would include:

identifying the widget as a tree view or similar upon setting focus to the tree;

identifying the expanded/collapsed state of node when it receives focus or is collapsed/expanded;

indicating the branch level of the current node when it receives focus;

indicating the number of sibling nodes at the current branch level and the position of the current node when it receives focus (e.g., announcing something like "two of four");

indicating the number of child nodes when an expanded node receives focus (e.g., announcing something like "three items").

Of the five screen readers tested, they each showed at least a few of these behaviours across the four examples, and in some cases almost all of them.

What's the Best Approach?

The Best Approach Example

The following tree view addresses the issues that were identified in testing and are described below. If you've got ideas on how to improve it or how better to address some of the shortcomings that certain screen reader and browser combinations have with simple ARIA tree views, please share.

This tree view works in all of the screen reader and browser combinations tested. Perhaps the biggest issue, as discussed below, is the verbosity experienced when navigating the tree with JAWS, NVDA, and Window-Eyes in Internet Explorer, an issue to which I've been unable to find an ideal solution that doesn't simultaneously cause more serious problems in other screen readers, notably VoiceOver.

The Tree

Basic Structure

Tree views are a way to navigate hierarchical lists, so in good semantic fashion, using list markup for the tree's basic structure is probably most appropriate. Some tree views use div and span elements, which works fine as long as ARIA and JavaScript are supported. If they aren't, then the user will be left with nothing but a bunch of nested div and span elements, which won't be helpful.

In this proposed best approach, the tree view uses an unordered list, and each list item has a nested link. The list items have role="treeitem", and implement aria-selected, aria-level, and aria-expanded if they have children. The nested link in each list item is set with role="presentation" and tabindex="-1". All of these attributes are added using JavaScript, since the functionality of the tree view depends on scripted arrow key controls.

The nested links could just as well be replaced with nested spans if your particular tree view doesn't call for using links. With this best practice example, there is no difference in behaviour in any of the screen readers except a relatively minor increase in verbosity in Window-Eyes where nested links versus spans is concerned.

The tree's hierarchical structure and the relationship between child leaf nodes and their parent are thus represented in the DOM's structure. That is, child node lists are children to the parent node list item. If the nested links were the nodes, each child node list would be a sibling to the parent node link, as opposed to its child. In the end, this actually doesn't seem to matter for JAWS, NVDA, or ORCA, which do fine whether child node lists are children to or siblings of the parent node in the DOM hierarchy. Window-Eyes doesn't mind nested spans as the nodes, but doesn't do well with nested links. And VoiceOver doesn't like nested links as nodes at all.

The tree uses aria-activedescendant (to support VoiceOver). Focus moves to and stays on the tree list itself. It does not move through the tree nodes. Instead, using the arrow keys to select or expand/collapse a node updates the nodes' aria-selected and aria-expanded attributes as appropriate, and the tree list's aria-activedescendant attribute. Pressing Enter or Space sets location to the href of the nested link whose id is referenced by aria-activedescendant.

Each child node list has role="group" to indicate that the items it contains form a distinct collection of nodes that is a descendant to the parent node in the tree hierarchy. At the same time, because child node lists are children to the parent node list item, this leads to issues with how Internet Explorer calculates the parent node's accessible name. See Internet Explorer and and Accessible Names below for more. Some of that problematic behaviour in IE is mitigated by setting aria-labelledby on the root tree ul to reference the heading preceding it. The rest of it can easily be solved using aria-labelledby to set the desired label for each node list item, but this causes some serious problems for VoiceOver in Safari. This suggests that once VoiceOver supports the roaming tabindex approach and can identify each node's number of children regardless of DOM structure, making the nested links the nodes could be a reasonable approach.

Addressing the Issues

VoiceOver/Safari and aria-activedescendant

Because VoiceOver in Safari currently only really supports tree views that use aria-activedescendant instead of a roaming tabindex, and a DOM structure that reflects node and child node relationships, the best approach example starts with that. Any other approach will simply not work properly with VoiceOver.

When using a roaming tabindex approach with VoiceOver, the arrow keys will move browser focus from node to node, and expand or collapse the nodes, but the VoiceOver cursor will not follow. That is, the VoiceOver cursor does not sync with the browser cursor. While VoiceOver will announce changes in the expanded/collapsed state of a node, it will not announce the label of the currently focussed node (unless the VoiceOver cursor also happens to be on that node).

It's not clear if this limitation is a bug with VoiceOver or with Safari, but Apple has been made aware of it. My suspicion is that it is a VoiceOver issue since, from what I can tell, even with the roaming tabindex approach, Safari is passing the appropriate values to the Mac OS X Accessibility Protocol.

It should also be noted that while the aria-activedescendant approach works quite well with VoiceOver when using the arrow keys, it effectively doesn't work at all with the VoiceOver navigation commands (Ctrl + Option + arrow keys). Nor does VoiceOver's command for opening and closing a disclosure triange (Ctrl+Option+\) work for expanding/collapsing a node.

Fortunately, the other four screen readers tested manage well enough with the aria-activedescendant approach.

Internet Explorer and Accessible Names

Internet Explorer creates an accessible name for some elements with a role attribute by concatenating the text of all the child elements. For example, if a list item with role="treeitem" has a nested list of 15 child nodes, upon setting focus to or selecting that list item, JAWS and NVDA will read not only that list item's text, but the text from every one of the 15 child node list items, whether they are expanded and visible or not. What's worse is that when focus moves from outside the tree to the tree or a node within the tree, NVDA and Window-Eyes will read every single node in the entire tree, expanded and visible or not. This rapidly gets distracting, especially in a tree with long child nodes branches. Try example A in IE with any of these screen readers and check it out for yourself.

In IE, when a newly selected node is in a different branch and part of a child list with role="group", NVDA reads every node within the grouping and adds the word "grouping". If we remove role="group" from the child list, then NVDA simply adds the word "list" instead of "grouping", but it still reads the group's list items. If we use aria-labelledby on the child list to reference the parent node's label, then NVDA uses that label as the name for the grouping, which is much less verbose, although this now causes NVDA in FF to introduce each child list as a grouping with the referenced label, which it didn't do at all before. With this solution, Orca in FF behaves similarly in that it introduces each child list as a "panel" instead of a "grouping", using the referenced label to specify the panel.

If we go one step further and use role="presentation" on the child list instead of role="group", then NVDA doesn't read the child grouping at all in IE. However, using role="presentation" on the child list instead of role="group" causes JAWS in FF, upon expanding a node with children, to read the label of every visible node within that child list. This could be seen as a small increase in verbosity for JAWS in FF for a significant decrease in verbosity for NVDA in IE. On the other hand, since most NVDA users probably play with Firefox, and role="group" is, I think, the appropriate ARIA markup for child lists of tree items, the best approach example here sticks with role="group" and applies aria-labelledby to reference the child list's parent node label.

Note that using div and span elements instead of a list doesn't solve this problem with how IE calculates the accessible name of node list items and how the screen readers relay that information.

Reading rule 2C of the ARIA Accessible Name Calculation, IE's behaviour would appear to be correct to some degree, though I'm not sure it should be including in that calculation descendants that are hidden via display:none and thus shouldn't be present in the accessibility tree. I'm also not sure that this is the right approach for tree items. I do think this makes sense for more discrete interactive elements like buttons and menu items, which one wouldn't expect to contain nested controls. However, I can see a case where tree items, just like normal list items, could reasonably contain one or more controls. In any case, correct behaviour or not, it does make for a rather verbose experience with screen readers in IE.

VoiceOver Doesn't Like the Solution

To address this, the ideal solution would be to make sure that both the main tree ul and each tree node li use aria-labelled to programmatically identify the appropriate accessible name for the tree and each node. In an ideal world, we could use aria-labelledby on the main ul to reference the heading preceding the tree, and on each node list item, aria-labelledby to reference the nested link.

I say "in an ideal world" because using aria-labelledby (or aria-label for that matter) on the nodes for some reason stops VoiceOver from announcing the name of the selected node. It also causes VoiceOver to call the tree view a list box, instead of a table as VoiceOver conventionally does with tree views, and with which I expect most VoiceOver users are familiar.

Using aria-labelledby on the main tree ul, however, appears to cause no problems, and has the benefit of preventing NVDA and Window-Eyes from reading every single node in the whole tree when focus first moves to the tree. However, not hearing the name of each node as one navigates through the tree with VoiceOver is a bit of a deal breaker, so the best approach proposed here does not use aria-labelledby on the nodes.

This is a significant compromise: To get a tree view that works in VoiceOver and across the other major screen readers means JAWS and NVDA users who prefer to work with Internet Explorer will get a much less pleasant experience. It remains usable, but is far from ideal.

Nested Links

Some tree views have links nested within the individual nodes, while others use nested links for the nodes themselves. Some trees don't include links at all, perhaps replacing them with nested spans. Again, each of these approaches can be made to work in one or more, but not all, screen readers. I've included nested links in the majority of the test examples because I think they have their use cases, and the use of links causes some issues with some screen readers that are worth noting. Links also provide a default, natively focussable and actionable element should, for example, JavaScript not be enabled.

VoiceOver and Nested Links

In VoiceOver, if a tree view using aria-activedescendant uses the nested links as the nodes instead of the list items, each node with children is announced as having zero enclosed child nodes, which is just incorrect. Using aria-owns, whether or not in combination with aria-setsize and aria-posinset, does not work to get VoiceOver to recognise the number of contained child nodes. For this reason, the proposed best approach keeps the list items as the nodes, instead of using the nested links as the nodes.

JAWS/Firefox and Nested Links

Links nested within the tree node list items prevent JAWS in Firefox from automatically entering forms mode, unless the links have role="presentation". As such, in our best approach example the nested links are set with role="presentation. If your tree view doesn't require nested links, which it may not depending on the purpose of your tree view and the functionality you're trying to provide, then there's no need to worry about this. For instance, if the tree items have nested spans instead of links, then JAWS in FF automatically enters forms mode just fine.

Window-Eyes and Nested Links

While role="presentation" on the nested links helps JAWS in Firefox automatically enter forms mode, it makes Window-Eyes more verbose. In the proposed best approach, which uses nested links with role="presentation", if the selected node is part of a child node branch, Window-Eyes in IE reads the labels of all of the nodes in that child branch, whether they are visible or not, just as JAWS and NVDA do in IE. If the links are swapped out for nested spans, then Window-Eyes behaves quite nicely, and no longer reads all of the child node names. This only happens in Window-Eyes, though. Whether nested links or spans, JAWS and NVDA behave the same with this best approach example in IE. Again, depending on your use case, you may want to go with nested spans instead of links.

Branch Level Numbering

Where indicating the branch level of a focussed node is concerned, some screen readers by default identify the root node branch as level zero, while others call it level one. In JAWS and VoiceOver, the top branch of the tree is considered level zero. NVDA in Firefox only, as well as Window-Eyes and Orca, consider the root node branch to be level one. NVDA in IE does not indicate the node's current branch level.

One can set aria-level on a node to identify the desired branch level, although this doesn't work in all screen readers. For example, setting aria-level="1" on first level nodes will get VoiceOver, as well as JAWS in IE but not in Firefox, to announce these nodes as being at level one, instead of at level zero. This provides for a more consistent behaviour across the screen readers, and so it's implemented in the best approach proposed here.

Indicating Child Containment

Only JAWS in IE, and VoiceOver indicate an expanded node's number of child nodes. It would be great if this could be addressed by using something like aria-posinset with aria-setsize, but these attributes don't appear to have any effect in any of the screen reader and browser combinations where the number of child nodes is not announced.

NVDA and Internet Explorer 10

In IE10, NVDA's interaction with tree views that use aria-activedescendant seems somewhat broken. This would appear to be an IE10 issue, as NVDA performs as expected in IE9. Basically, in IE10, NVDA does not automatically enter application mode when focus is set to a node in the tree. Automatic application mode can be forced by setting role="application" on the tree's container, but this doesn't make the rest of the tree usable with NVDA in IE10. Even if one manages to get into application mode, full access to every node in the tree doesn't seem possible.

So this is another significant compromise: A tree view that works in all the screen readers tested won't work in any usable fashion with NVDA in IE10. At the same time, this likely isn't the worst thing in the world as most NVDA users probably work with Firefox.

Putting it all Together

Given all of the issues described above, I've tried to propose an approach to constructing tree views that will work in all popular screen reader and browser combinations while minimising the ill effects of each screen reader's particular behaviour. I'm not quite happy with it, as it presents some unpleasant behaviour for screen readers in IE in order to support significant limitations in VoiceOver's handling of tree views.

Again, if you've got another approach that you think manages these issues better, or if you notice something I've missed, please let me know. There's so much variation in both the ways one can mark up a tree view and how different screen readers and browsers deal with them that it's not unlikely that I've overlooked something.

Testing Actual Screen Reader Behaviour

To test screen reader support for WAI-ARIA tree views, I created four simple examples. Actually, I created many more examples, just to try or rule out potential solutions to some of the problems discovered. But I'm only including the four examples below as they reveal the main issues my proposed best approach attempts to address.

Each of the examples is a simple, single-selection tree view, as opposed to a multiple-selection tree where more than one item can be selected at the same time. In these examples, only one tree item can be selected at a time. This means that the tree item with focus is also identified as selected using aria-selected="true".

Note that in the result tables for each example, unless otherwise indicated, JAWS' behaviour in IE9 is the same as its behaviour in IE10. The same goes for NVDA in IE9 and IE10.

Also make sure to read the notes associated with each example's test results as these discuss additional behaviours and issues not covered in the result tables.

Example A

Example A uses a simple unordered list with role="tree". The individual list items are assigned role="treeitem", and have nested span elements. Using the basic keyboard commands, focus moves from list item to list item. The list item or node with focus has aria-selected="true", while all other nodes are set to aria-selected="false".

The open or closed state of nodes with children is visually indicated with fairly standard plus and minus icons loaded as CSS background images on an empty div. While probably not necessary, to ensure that they are hidden from screen readers, aria-hidden="true" is used. Mouse users can click on these icons to toggle the expanded/collapsed state of the node. Keyboard users, of course, use the arrow keys.

When a node with children is expanded, its aria-expanded attribute is changed from "false" to "true", and the visible toggle icon is updated from a plus sign to a minus sign to reflect the state of the node. The child list (role="group") that was previously hidden using display:none, is set to display:block. The use of aria-hidden is not required as display:none already serves to hide that content from all users and assistive technologies.

This pattern of updating the relevant attributes on nodes and groups of child nodes occurs in more or less the same manner with all of the examples presented here.

Result Notes for Example A

JAWS and Example A

In both IE and FF, upon entering the tree view, JAWS provides interaction instructions, "To move through or expand items, use the arrow keys." This behaviour is common for JAWS in all of the examples presented here.

In IE only, upon setting focus to or expanding/collapsing a node with children, JAWS reads every child node, whether it is expanded and visible or not. This is rather verbose and distracting, and has to do with an interesting concatenation that IE does with the accessible name of some elements set with a role attribute. Effectively, the accessible name for the li with role="treeitem" becomes a string containing the text from every child element, including nested child lists hidden using display:none. Setting aria-labelledby to the id of the list item's nested span element does solve this.

Reading rule 2C of the ARIA Accessible Name Calculation, IE's behaviour would appear to be correct, though I'm not sure it should be including in that calculation descendants that are hidden via display:none and thus shouldn't be present in the accessibility tree.

NVDA and Example A

When first setting focus to any node in the tree in IE, NVDA reads every single node in the tree, expanded and visible or not. Again, using aria-labelledby on the main tree list solves this.

When focus changes branch and is moved to a node in a child list with role="group", it reads every node within the grouping and adds the word "grouping". If we remove role="group" from the child list, then NVDA simply adds the word "list" instead of "grouping", but still reads the group's list items. If we use aria-labelledby on the child list to reference the parent node's label, then NVDA uses that label as the name for the grouping, which is much less verbose. Even better, if we use role="presentation" on the child list instead of role="group", then NVDA doesn't read the child grouping. However, this causes JAWS in FF, upon expanding a node with children, to read the label of every visible child node.

Further, in IE, upon setting focus to a node with children, NVDA reads every child node, whether it is expanded and visible or not. This is in addition to NVDA first reading all of the nodes in a child list with role="group" when focus first moves to a node in that grouping.

NVDA does not indicate the number of child items under an expanded node. Using aria-setsize doesn't have any effect.

Window-Eyes and Example A

In IE, upon first setting focus to a node within the tree, Window-Eyes reads every single one of the tree's nodes, visible and expanded or not. Setting aria-labelledby on the tree's main ul stops this from happening.

After moving focus to a node in a different branch, Window-Eyes announces the node's branch level, which it calls "depth".

VoiceOver and Example A

Interestingly, VoiceOver identifies trees as tables, and identifies the equivalent row number of the selected node. This is at least consistent with how VoiceOver deals with tree views in the Finder in Mac OS X, so I expect VoiceOver users will be familiar with it. Still, I find it a little odd.

Navigating with the arrow keys moves browser focus from node to node, but the VoiceOver cursor doesn't follow the browser focus. You can expand/collapse nodes using the arrow keys and VoiceOver will announces which row number is expanded/collapsed, but not the text content of the node, making this tree view example effectively unusable in this case. It is not clear if this is a WebKit or a VoiceOver bug, but Apple has been made aware of it.

Conversely, using the VoiceOver navigation commands to move the VoiceOver cursor from node to node does cause VoiceOver to announce the node's name, position and expanded/collapsed state, but browser focus does not follow and keep sync with the VoiceOver cursor. The VoiceOver command (Ctrl+Option+\expandrequest and collapserequest IndieUI events are supported.

Orca and Example A

Orca does not indicate the number of child items under an expanded node. Using aria-setsize doesn't have any effect.

Orca does not indicate the number of sibling nodes at the current branch level or the position among them of the node with focus. Using aria-posinset with aria-setsize has no effect.

Example B

Example B is an unordered list with role="tree", like example A, but the list items have nested links, and it is the links that are set with role="treeitem". Focus moves through the links, and the links' aria-selected values are updated accordingly. To prevent the semantics of the list items interfering, each li has role="presentation.

Note that child node groupings are not nested as direct descendants of their parent nodes, which are the links with @role="treeitem". Instead, they are siblings to the link. This doesn't seem to hamper JAWS, NVDA, or ORCA, which assign appropriate relationships between tree items and their sub-node groupings. It may have some effect in VoiceOver, but VoiceOver doesn't work with this tree view structure in any case.

Test Results for Example B

Screen Reader Behaviour with Tree View Example B

Screen Reader/Browser

Enters Application Mode on Focus

Identifies Tree View or Similar

Identifies Node's Expanded/Collapsed State

Indicates Node's Branch Level

Indicates Node's Position Among Sibling Nodes

Indicates Number of Expanded Node's Children

JAWS in IE9/10

Yes

Yes, but only in Browse Mode

Yes

Yes

Yes, but only for collapsed or end nodes

Yes

JAWS in FF19

Yes

Yes, but only in Browse Mode

Yes

Yes

Yes

No

NVDA in IE9/10

Yes

Yes

Yes

No

No

No

NVDA in FF19

Yes

Yes

Yes

Yes

Yes

No

Window-Eyes in IE10

No

Yes

Yes

Yes

Yes

No

Window-Eyes in FF19

Yes

Yes

Yes

Yes

Yes

No

VoiceOver in Safari 6.0.2

N/A

Tree identified as a table

No

No

No

No

Orca in FF19

N/A

Yes

Yes

Yes

No

No

Result Notes for Example B

JAWS and Example B

Results for example B are the same as those for example A, with one major exception: Because the nested links, and not the list items, are set with role="treeitem", setting focus to or expanding/collapsing a node does not cause JAWS to read every one of its child nodes.

NVDA and Example B

Results are the same as those for example A, except that additionally, in IE, when setting focus to a node, which is a link in this example, NVDA reads the link's href value.

Window-Eyes and Example B

Results for Window-Eyes in example B are the same as in example A, except that in IE:

When first setting focus to a node in the tree, Window-Eyes does not automatically enter forms mode; and,

When focus is set to a node, Window-Eyes, like NVDA, reads the link's href value.

VoiceOver and Example B

VoiceOver's behaviour in example B is the same as in example A.

Orca and Example B

Orca's behaviour in example B is the same as in example A.

Example C

Example C is like example B, in that the list items have nested links. In this case, however, the list items are the nodes and set with role="treeitem". Focus moves through the list items. Activating a tree item by pressing Enter or Space sets location to the nested link's href value.

Result Notes for Example C

JAWS and Example C

In IE, as with example A, JAWS reads all child nodes, expanded and visible or not, when setting focus to or expanding/collapsing a node with children.

In FF, upon first setting focus to a node in the tree, JAWS will automatically enter forms mode only if the links nested within the list items are set with role="presentation". Otherwise, one needs to manually enter forms mode by hitting Enter on the focussed node. Alternatively, one can turn off JAWS' Virtual PC Cursor. Using nested spans instead of nested links avoids this issue.

NVDA and Example C

NVDA's behaviour in example C is the same as in example A.

Window-Eyes and Example C

Window-Eyes' behaviour in example C is the same as in example A.

VoiceOver and Example C

VoiceOver's behaviour in example C is the same as in examples A and B.

Orca and Example C

Orca's behaviour in example C is the same as in examples A and B, with one major exception: Once focus is set to an end node that contains a link, focus is set to the link itself. At that point, the arrow keys will move focus from link to link, but not node to node, thereby requiring Orca navigation commands (Orca Modifier + arrow keys) to actually change node, while just the the left and right arrow keys continue to expand/collapse nodes with children. However, setting role="presentation" on the nested links solves this problem for Orca. This is not a big deal since the links aren't focussable anyway because they are set with tabindex="-1" in order to force focus to the list items, which act as the tree nodes.

Example D

Example D is like example C, in that the list items are the tree items, but departs from the three previous examples in that it uses aria-activedescendant. Focus does not roam through the tree nodes, but instead stays on the tree itself. With focus on the tree, using the arrow keys updates the tree's aria-activedescendant value to the id of the selected node, which is set with aria-selected="true". Pressing Enter or Space sets location to the href value of the nested link whose id is referenced by aria-activedescendant.

Result Notes for Example D

JAWS and Example D

Behaviour in example D is the same as in example C: In FF, the links nested within the list item tree nodes need role="presentation", else JAWS does not automatically enter forms mode. However, in FF, without role="presentation" on the links, hitting Enter on the currently selected node only serves to immediately toggle forms mode on and off again at the same time. However, manually turning off JAWS's Virtual PC Cursor does the job for providing access to the tree view. Again, if nested spans are used instead of links, this is not an issue.

NVDA and Example D

In IE9, NVDA performs effectively the same as it does in examples A and C. However, in IE10, interaction with this example tree view seems somewhat broken. This would appear to be an IE10 issue, as NVDA performs as expected in IE9. Basically, in IE10, NVDA does not automatically enter application mode when focus is set to the tree. Even if one manages to get into application mode (by pressing Down arrow and then Tab once focus is set to the tree container), full access to every node in the tree doesn't seem possible.

In a quick test using IE10 with a variation on example D without nested links within the list items, as can be found with James Craig's ARIA tree view example, the results are much the same: While James' example uses role="application", which automatically forces NVDA into application mode, it remains that none of the node's text is announced by NVDA, making the tree view effectively unusable.

In IE9, upon selecting (aka setting aria-activedescendant to the id of) a node with children, NVDA reads all child nodes, expanded and visible or not. It also does this in this example whenever expanding or collapsing a node. Setting aria-labelledby to the id of the nested link's text solves this, although doing so causes serious issues with VoiceOver. And even then, child lists with role="group" still get read out by NVDA when a node in a new child list group is selected. If desired, this could be addressed by setting aria-labelledby on the child list's ul, or using role="presentation" instead of role="group".

In FF, NVDA's behaviour with example D is the same as with examples A, B, and C.

Window-Eyes and Example D

Window-Eyes' behaviour in example D is the same as in examples A and C.

VoiceOver and Example D

This is the only example in which VoiceOver works with the tree view. It would seem that, at least for the time being, VoiceOver does not support the roaming tabindex approach where focus actually moves from node to node, but requires the use of aria-activedescendant on the tree container to point to the currently selected node.

Since VoiceOver identifies the tree as a table, when a selected node with children is expanded/collapsed, VoiceOver announces the number of rows that have been added or removed, and the row number of the currently selected node.

When focus is first set to the tree view, VoiceOver does not announce the label of the currently active descendant. Once the active descendant node changes via the arrow keys, however, then VoiceOver announces its label. Since adding aria-labelledby to the nodes causes problems with VoiceOver, I've not found a way to overcome this slight limitation.

Orca and Example D

Orca's behaviour in example D is the same as in examples A and B.

Acknowledgements

Many thanks to James Craig for advice on ARIA tree views in general and providing an example that worked in Safari with VoiceOver, and to Todd Kloots for helpful insights and discussion, and testing VoiceOver in Mac OS X Lion.

Translations

Note: The translations below are listed with no guarantee of their accuracy (or the accessibility of their content).

6 Responses to (Not so) Simple ARIA Tree Views and Screen Readers

Wow, what a heap of work you did there! Glad to see Orca in the list too.

My opinion at this point is that while it’s good that you found something that “works” somewhat okay in current browsers and AT (which is what anyone building these things today in a setting demanding utmost accessibility needs), if I were building a tree for the future I would rather build it in the ideal way, with the assumption that the bugs (and yeah I see VO’s issue with links as absolutely a bug) will be fixed… even though this can be a dangerous assumption if you don’t know you’ll be keeping that page updated.

You should write more often, Jason. Although I can appreciate that it can’t always be as extensive as this, or you would not have time for anything else!

I’m working on a multi-part guide to ARIA at the moment, so when I get to this topic, I will definitely link to your article. Super useful stuff, as usual.

(Off-topic: Your comment instructions should mention that your website insists on support for JS and cookies to post a comment. It’s annoying that it requires them; more so that it is not known before posting.)

I can appreciate your position. I prefer to not concern myself with bugs in AT or browsers where they prevent me from using certain technologies. In this case, the proposed workaround or “best approach” for tree views works now and *should* continue to work into the future, barring the introduction of new bugs. Granted, I don’t think it’s the best tree view approach, so I guess if one wanted to build that, then they’d have to put up with the effects of the bugs. Like so many of the technical accessibility choices we make, there’s a judgement call.

Great article Jason. It’s sad that it has to be such a challenge to achieve consistent support across browser / AT combinations for composite widgets like this. Maybe we can collaborate some time to improve this.

Thanks, Hans. It is a sad current state of affairs, but based on browser and AT progress over the past few years with ARIA, I’m remaining optimistic. And yes, definitely open to collaborating on improving the situation.