Tree View
Hierarchical data structures like file browsers, category trees, and nested settings
Overview
A tree view presents hierarchical data as an expandable and collapsible structure. The most common mistake is building trees from nested <ul> lists with click handlers on generic elements. These miss the required ARIA tree roles, expanded/collapsed states, level information, and keyboard navigation that screen reader and keyboard users depend on.
WCAG Criteria:
- 1.3.1 Info and Relationships — the hierarchy and nesting levels must be programmatically conveyed through ARIA roles and properties
- 2.1.1 Keyboard — arrow keys must navigate the tree, expand/collapse nodes, and move between levels
- 4.1.2 Name, Role, Value — each item must expose its role (
treeitem), expanded state, level, and position
Key requirements:
- The container needs
role="tree"with an accessible label - Each item needs
role="treeitem"; browsers can derive level, set size, and position from complete DOM ownership, while virtualized or incomplete trees may need authoredaria-level,aria-setsize, andaria-posinset - Parent nodes need
aria-expanded="true"or"false"— leaf nodes must NOT havearia-expanded - Child groups are wrapped in an element with
role="group" - Use roving
tabindex— the focused item hastabindex="0", all others havetabindex="-1" - Arrow keys: Up/Down move between visible items, Right expands or moves to first child, Left collapses or moves to parent
- Home/End jump to the first/last visible item in the tree
- Enter/Space activate the current item
File Browser Tree
Accessible Tree View vs. Unsemantic Nested Lists
Inaccessible
Files
-
▼📁 Documents
- 📄 Resume.pdf
- 📄 Cover Letter.docx
- ▶📁 Images
View inaccessible code
<!-- No tree roles, no aria-expanded, no keyboard navigation -->
<div class="file-tree">
<ul>
<li>
<span class="folder" onclick="toggleFolder(this)">
Documents
</span>
<ul style="margin-left: 20px">
<li><span>Resume.pdf</span></li>
<li><span>Cover Letter.docx</span></li>
</ul>
</li>
<li>
<span class="folder" onclick="toggleFolder(this)">
Images
</span>
<ul style="margin-left: 20px; display: none">
<li><span>Photo.jpg</span></li>
</ul>
</li>
</ul>
</div>Accessible
Files
-
📁 Documents
- 📄 Resume.pdf
- 📄 Cover Letter.docx
- 📁 Images
View accessible code
<ul role="tree" aria-label="File browser">
<li role="treeitem" aria-expanded="true"
aria-level="1" aria-setsize="2" aria-posinset="1"
tabindex="0">
Documents
<ul role="group">
<li role="treeitem"
aria-level="2" aria-setsize="2" aria-posinset="1"
tabindex="-1">
Resume.pdf
</li>
<li role="treeitem"
aria-level="2" aria-setsize="2" aria-posinset="2"
tabindex="-1">
Cover Letter.docx
</li>
</ul>
</li>
<li role="treeitem" aria-expanded="false"
aria-level="1" aria-setsize="2" aria-posinset="2"
tabindex="-1">
Images
<ul role="group" hidden>
<li role="treeitem"
aria-level="2" aria-setsize="1" aria-posinset="1"
tabindex="-1">
Photo.jpg
</li>
</ul>
</li>
</ul>
<script>
// Roving tabindex: focused item tabindex="0", rest "-1"
// ArrowDown/Up: move between visible treeitems
// ArrowRight: expand closed node or move to first child
// ArrowLeft: collapse open node or move to parent
// Home/End: first/last visible item
// Enter/Space: activate (toggle expand/collapse)
</script>What’s wrong with the unsemantic nested lists?
- No
role="tree"on the container — screen readers cannot identify the widget as a tree - List items have no
role="treeitem"— their hierarchical role is invisible to assistive technology - No
aria-expandedon folders — users cannot tell whether a node is open or closed - No
aria-level,aria-setsize, oraria-posinset— the nesting depth and position are not conveyed - No keyboard navigation — arrow keys do not work, so keyboard-only users are stranded
- Indentation is purely visual (CSS margin) — the hierarchy is lost for non-visual users
Expected programmatic facts:
| Version | Name, role, state, and position |
|---|---|
| Inaccessible (plain list) | Text is present, but there is no tree role, expanded state, or managed focus. |
| Accessible parent | Exposes the item name, treeitem role, expanded/collapsed state, level, and position. |
| Accessible leaf | Exposes its name, treeitem role, level, and position without a false expanded state. |
Key teaching points:
- Roving tabindex, not
aria-activedescendant— roving tabindex (movingtabindex="0"to the active item and setting all others totabindex="-1") provides more reliable VoiceOver support thanaria-activedescendant - Never add
aria-expandedto leaf nodes — only parent items that can be expanded or collapsed should havearia-expanded. Adding it to leaf nodes falsely tells screen readers the item has children - Position properties can be derived — complete owned DOM trees generally let browsers compute level, set size, and position; author them when virtualization or incomplete ownership prevents that calculation
- Focus must remain visible — the demo uses a 3px outline, preserves it in both themes, and maps it to the system Highlight color in forced-colors mode
- Right/Left arrows for expand/collapse — this is the expected keyboard convention for trees. Right arrow expands a closed node or moves to its first child; Left arrow collapses an open node or moves to its parent