Loading Developer Playground

Loading ...

Skip to main content
ARIA ROLEComposite RolesHierarchical selectionExpandable itemsaria-level awareness

tree

Displays a hierarchical set of options or folders. Nodes can be expanded or collapsed to reveal nested treeitems.

Live Example

Section Tree

Expandable navigation tree with Arrow key interactions and nested groups.

Purpose

Navigate nested structures such as file systems or navigation trees.

Structure

role="tree" contains treeitem elements, optionally grouped inside role="group".

Behavior

Arrow keys expand/collapse nodes and move between siblings.

When to Use

1

File explorers

List folders and files with nested items.

2

Navigation outlines

Provide drill-down navigation for docs or settings.

3

Permissions matrix

Enable selecting nested permissions or features.

Required Structure

Treeitems

Each treeitem needs an id, aria-level, aria-expanded (if parent), and optionally aria-selected.

Groups

Nested children appear inside role="group" containers positioned after their parent treeitem.

Label exposure

Use aria-labelledby or include text content within the treeitem for naming.

Keyboard Interaction Model

ActionKeysResult
Move between siblingsArrowUp / ArrowDownMoves to previous/next visible treeitem.
Expand nodeArrowRightExpands focused node or moves to first child if already expanded.
Collapse nodeArrowLeftCollapses focused node or moves to parent if already collapsed.
Jump to edgesHome / EndMoves to the first or last visible node.
TypeaheadCharacter keysMoves focus to the next item starting with typed characters.

Required States & Properties

aria-levelRequired

1-based depth of the node. Update dynamically when nodes move.

aria-expandedOptional

Only on items that own children. Omit when the node is a leaf.

aria-setsizeOptional

Total number of siblings for a node. Helpful when nodes are virtualized.

aria-posinsetOptional

Position of the node within its set of siblings (1-based).

aria-selectedOptional

True when the node is selected. Useful for multi-select trees.

Implementation Checklist

  • Maintain roving focus so only one treeitem is tabbable.

  • Update aria-level/posinset/setsize when dynamically inserting or removing nodes.

  • Provide visual affordances (chevrons) for expandable nodes and match them with aria-expanded state.

  • Scroll newly focused nodes into view to avoid hidden focus indicators.

Code Examples

Nested Tree Structure

Simple outline showing aria-level and aria-expanded.

<ul role="tree" aria-label="Project files">
  <li role="treeitem" aria-expanded="true" aria-level="1" aria-setsize="2" aria-posinset="1">
    src
    <ul role="group">
      <li role="treeitem" aria-level="2" aria-setsize="2" aria-posinset="1">components</li>
      <li role="treeitem" aria-level="2" aria-setsize="2" aria-posinset="2">pages</li>
    </ul>
  </li>
  <li role="treeitem" aria-expanded="false" aria-level="1" aria-setsize="2" aria-posinset="2">
    tests
  </li>
</ul>

Roving Focus Tree

Scripted keyboard handler for expand/collapse.

const tree = document.querySelector('[role="tree"]')
const items = Array.from(tree.querySelectorAll('[role="treeitem"]'))

function focusItem(index) {
  items.forEach((item, i) => (item.tabIndex = i === index ? 0 : -1))
  items[index].focus()
}

let active = 0
focusItem(active)

tree.addEventListener('keydown', (event) => {
  const current = items[active]
  if (event.key === 'ArrowDown') {
    event.preventDefault()
    active = Math.min(active + 1, items.length - 1)
    focusItem(active)
  } else if (event.key === 'ArrowUp') {
    event.preventDefault()
    active = Math.max(active - 1, 0)
    focusItem(active)
  } else if (event.key === 'ArrowRight') {
    if (current.getAttribute('aria-expanded') === 'false') {
      current.setAttribute('aria-expanded', 'true')
    }
  } else if (event.key === 'ArrowLeft') {
    if (current.getAttribute('aria-expanded') === 'true') {
      current.setAttribute('aria-expanded', 'false')
    }
  }
})

Best Practices

🧭

Breadcrumb hints

Show the current path outside the tree for context.

🪄

Lazy loading cues

If nodes load async, show a spinner and set aria-busy.

📏

Consistent indentation

Match visual indentation with aria-level values.

Common Mistakes

Mismatched levels

aria-level values do not match the visual hierarchy, confusing screen reader output.

Update aria-level whenever nodes are re-parented.

Collapsing removes nodes

Collapsed nodes are removed from the DOM and lose their ids.

Keep nodes in place and use hidden attribute to collapse.

Related Roles & Patterns

group

Container wrapping nested children.

Learn more

treegrid

Hybrid role combining tree and grid.

Learn more