Skip to Content
Component ExamplesTree View

Tree View

Hierarchical data structures like file browsers, category trees, and nested settings

Maintained by Martin KrugerLast reviewed

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:

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 authored aria-level, aria-setsize, and aria-posinset
  • Parent nodes need aria-expanded="true" or "false" — leaf nodes must NOT have aria-expanded
  • Child groups are wrapped in an element with role="group"
  • Use roving tabindex — the focused item has tabindex="0", all others have tabindex="-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
    • 📸 Photo.jpg
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
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-expanded on folders — users cannot tell whether a node is open or closed
  • No aria-level, aria-setsize, or aria-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:

VersionName, role, state, and position
Inaccessible (plain list)Text is present, but there is no tree role, expanded state, or managed focus.
Accessible parentExposes the item name, treeitem role, expanded/collapsed state, level, and position.
Accessible leafExposes its name, treeitem role, level, and position without a false expanded state.

Key teaching points:

  • Roving tabindex, not aria-activedescendant — roving tabindex (moving tabindex="0" to the active item and setting all others to tabindex="-1") provides more reliable VoiceOver support than aria-activedescendant
  • Never add aria-expanded to leaf nodes — only parent items that can be expanded or collapsed should have aria-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

Resources