7.4: Tree Menus
Tree menus often have the same underlying HTML structure as a menu bar, but rather than being arranged in a horizontal layout, they tend to be arranged vertically.
WAI-ARIA roles, states, and properties used in a tree menu
- tabindex = [0 | -1]
- aria-labelledby = [instruction div id | title div id]
- aria-hidden = [true | false]
- role = "tree"
- role = "treeitem"
- role = "presentation"
- aria-level = [number of parent ULs]
- aria-setsize = [number of LIs in a level]
- aria-posinset = [position of each LI in a set]
- aria-expanded = [true | false]
- aria-selected = [true | false]
The following JSFiddle presents a typical tree menu widget with a few submenus. Review the JavaScript and HTML markup. Test the tree menu presented under the Result tab with ChromeVox to understand how it functions without any accessibility features added. You can work in JSFiddle itself by clicking “Edit in JSFiddle” at the top, right-hand side, copying the accessibility/WAI-ARIA code described below to fix the accessibility of the tree menu before completing Activity 13 on the page that follows.
First, define instructions on using the tree menu with a keyboard.
Within the
init()
function add keyboard focus to the tree container by applying
tabindex="0"
to it, and label the container with the instructions created above, which gets read by screen readers when the menu initially receives focus.
Within the
init()
function, hide the instructions
<div>
from screen readers by default by setting
aria-hidden="true"
when the tree menu is initialized.
Within the
init()
function replace the unordered list semantics with tree menu semantics using
role="tree"
, and give it a title using
aria-labelledby
to reference the title defined in the default options.
Within the
init()
function, define menu items with
role="treeitem"
, remove all keyboard access by default with
tabindex="-1"
, set the number of levels in the tree based on the number of parent ULs with
aria-level=[number of ULs]
, set the number of tree items on a given level with
aria-setsize="[number of LIs in a UL]"
, and finally define the position of each tree item within a level using
aria-posinset="[child LI index]"
.
Within the
init()
function, if a tree item has a submenu UL that has been opened, set
aria-expanded="true"
, otherwise set
aria-expanded="false"
.
Within the
init()
function, for each tree item use the text of the associated
span
element as its label. To ensure both the label and the contents of the
span
element are not both read, assign
role="presentation"
to the
span
.
Within the
init()
function, where mouse
onclick
functionality is used, provide equivalent
keydown
functionality, here referencing the
onKeyDown
function, shown below, that defines the keys to operate the menu.
Within the
init()
function, right after adding
keydown
operability, make the first item in the tree menu focusable by adding
tabindex="0"
to the first
li
.
Within the
selectItem()
function, set up a roving tabindex, while at the same time applying
aria-selected=[true | false]
when tree items receive or lose focus.
In the
toggleSubmenu()
function, announce the state of submenus to the screen reader by toggling the
aria-expanded=[true | false]
attribute when a menu is opened or closed.
Adding Keyboard Operability
Much like the menu bar described in the previous activity, keyboard operability for a tree menu can be complex, with various operations using multiple key strokes to perform the same function. W3C describes potential keyboard operation in the WAI-ARIA Authoring Practices 1.1 , reproduced below.
Tree Menu Keyboard Interaction
For a vertically oriented tree:
-
When a single-select tree receives focus:
- If none of the nodes are selected before the tree receives focus, focus is set on the first node.
- If a node is selected before the tree receives focus, focus is set on the selected node.
-
When a multi-select tree receives focus:
- If none of the nodes are selected before the tree receives focus, focus is set on the first node.
- If one or more nodes are selected before the tree receives focus, focus is set on the first selected node.
-
Right Arrow:
- When focus is on a closed node, opens the node; focus does not move.
- When focus is on a open node, moves focus to the first child node.
- When focus is on an end node, does nothing.
-
Left Arrow:
- When focus is on an open node, closes the node.
- When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node.
- When focus is on a root node that is also either an end node or a closed node, does nothing.
- Down Arrow: Moves focus to the next node that is focusable without opening or closing a node.
- Up Arrow: Moves focus to the previous node that is focusable without opening or closing a node.
- Home: Moves focus to the first node in the tree without opening or closing a node.
- End: Moves focus to the last node in the tree that is focusable without opening a node.
- Enter: Activates a node, i.e., performs its default action. For parent nodes, one possible default action is to open or close the node. In single-select trees where selection does not follow focus (see note below), the default action is typically to select the focused node.
-
Type-ahead is recommended for all trees, especially for trees with more than 7 root nodes:
- Type a character: focus moves to the next node with a name that starts with the typed character.
- Type multiple characters in rapid succession: focus moves to the next node with a name that starts with the string of characters typed.
- * (Optional): Expands all siblings that are at the same level as the current node.
-
Selection in multi-select trees:
Authors may implement either of two interaction models to support multiple selection: a recommended model that does not require the user to hold a modifier key, such as Shift or Ctrl, while navigating the list or an alternative model that does require modifier keys to be held while navigating in order to avoid losing selection states.
-
Recommended selection model – holding a modifier key while moving focus is not necessary:
- Space: Toggles the selection state of the focused node.
- Shift + Down Arrow (Optional): Moves focus to and toggles the selection state of the next node.
- Shift + Up Arrow (Optional): Moves focus to and toggles the selection state of the previous node.
- Shift + Space (Optional): Selects contiguous nodes from the last selected node to the current node.
- Ctrl + Shift + Home (Optional): Selects the node with focus and all nodes up to the first node.
- Ctrl + Shift + End (Optional): Selects the node with focus and all nodes down to the last node.
- Ctrl + A (Optional): Selects all nodes in the tree. Optionally, if all nodes are selected, it can also unselect all nodes.
-
Alternative selection model – moving focus without holding the Shift or Ctrl modifier unselects all selected nodes except for the focused node:
- Shift + Down Arrow: Moves focus to and toggles the selection state of the next node.
- Shift + Up Arrow: Moves focus to and toggles the selection state of the previous node.
- Ctrl + Down Arrow: Without changing the selection state, moves focus to the next node.
- Ctrl + Up Arrow: Without changing the selection state, moves focus to the previous node.
- Ctrl + Space: Toggles the selection state of the focused node.
- Shift + Space (Optional): Selects contiguous nodes from the most recently selected node to the current node.
- Ctrl + Shift + Home (Optional): Selects the node with focus and all nodes up to the first node.
- Ctrl + Shift + End (Optional): Selects the node with focus and all nodes down to the last node.
- Ctrl + A (Optional): Selects all nodes in the tree. Optionally, if all nodes are selected, it can also unselect all nodes.
-
Recommended selection model – holding a modifier key while moving focus is not necessary:
- DOM focus (the active element) is functionally distinct from the selected state. For more details, see this description of differences between focus and selection.
-
The
tree
role supports the aria-activedescendant property, which provides an alternative to moving DOM focus amongtreeitem
elements when implementing keyboard navigation. For details, see Managing Focus in Composites Using aria-activedescendant . - In a single-select tree, moving focus may optionally unselect the previously selected node and select the newly focused node. This model of selection is known as “selection follows focus”. Having selection follow focus can be very helpful in some circumstances and can severely degrade accessibility in others. For additional guidance, see Deciding When to Make Selection Automatically Follow Focus .
- If selecting or unselecting all nodes is an important function, implementing separate controls for these actions, such as buttons for “Select All” and “Unselect All”, significantly improves accessibility.
-
If the nodes in a tree are arranged horizontally:
- Down Arrow performs as Right Arrow is described above and vice versa.
- Up Arrow performs as Left Arrow is described above and vice versa.
Source: WAI-ARIA Authoring Practices 1.1
For the tree menu created here, we’ve added in basic keyboard operability. Keyboard operation includes: Up and Down, and Left and Right Arrows for navigating within the tree, and the Enter or Space bar keys to toggle submenus open or closed. The Tab key by default enters and exits the tree menu and does not need to be defined as part of the keyboard operability of the tree menu.
Accessible Tree Menu in Action
Watch the following video showing ChromeVox interacting with a tree menu. The Tab key is used to navigate into the tree menu, to the first tree item, and to exit the tree menu. The Up and Down arrows are used to move between tree items. The Space bar or Enter key are used to expand and collapse a tree item with a submenu. When a submenu is opened, focus moves to the first tree item in the menu. Aim to have the tree menu you update in Activity 13 operate and announce itself like the one in the video.
Video: Accessible Tree Menu