Why tree views demand special attention
Tree views present a well-known accessibility challenge. They pack nested navigation, expandable branches, and selectable nodes into a single interface — and they must behave predictably across a wide range of assistive technologies. GitHub's file tree is a prime example of such a component in production. The approach taken to make it accessible offers useful patterns for others building similar interfaces.

On GitHub, the tree view mirrors the folder-and-file hierarchy familiar from operating system file explorers. It's a list of lists, and that foundation shapes the entire accessibility strategy.
Grounding the interaction model in Windows File Explorer
For components with complex interaction requirements, it pays to model behavior on patterns people already know. Windows File Explorer's tree view was the natural reference here, given that Windows is the dominant platform among desktop screen reader users. Testing with NVDA and JAWS against the native implementation clarified how to handle focus management, keypresses, and expected announcements.

The ARIA Authoring Practices Guide (APG) can be a helpful starting point for thinking through the overall pattern. However, it is no longer recognized by the W3C as a formal document, so its code suggestions warrant scrutiny before being adopted wholesale.
Semantic structure first
Because a tree view is inherently a nested list, GitHub's implementation builds on ul and li elements rather than generic divs or spans.
<ul>
<li>
<ul>
<li>.github/</li>
<li>source/</li>
<li>test/</li>
</ul>
</li>
<li>.gitignore</li>
<li>README.md</li>
</ul>
This choice has several practical benefits:
- It produces a more meaningful accessibility tree for screen readers.
- It reduces the maintenance burden and the risk of regressions when updates are made.
- It improves interoperability across browsers, operating systems, and assistive technology combinations.
Using semantic elements also improves support beyond the most common screen reader pairings. Testing showed better results on less-common assistive technologies, including some running on lower-power devices like entry-level Android smartphones.
There's another benefit: Forced Color Mode. Semantic elements map to native operating system UI patterns, so the heuristics browsers use for forced colors recognize them without extra work. Recreating that with div and span elements would require manual styling and ongoing maintenance.
One caveat worth noting: GitHub does not currently virtualize its file trees. If that ever changes, this architectural decision would need revisiting, since virtualized rendering fundamentally changes how the accessibility tree and focus are managed.
Composite widget behavior
Treating the tree view as a composite widget means the entire component requires a single tab stop. That matters for real-world use. A repository can contain hundreds of files across dozens of directories; without this treatment, keyboard users would need to press Tab through every node to move past the tree and reach the rest of the page. The composite approach also allows role="tree" and role="treeitem" to describe the structure properly, which HTML alone cannot express.
Given that the file tree is a primary way to navigate repository content, the component is also wrapped in a nav landmark. This helps people using screen reader landmark navigation move directly to the file tree — particularly useful on larger pages with many sections. This does not mean every tree view should be a landmark; it depends on whether the tree serves as a primary navigation mechanism.
Managing focus with a roving tabindex
A roving tabindex is the mechanism behind the single-tab-stop behavior. Each element in the tree gets tabindex="-1", and when someone navigates through the tree with the keyboard, the element currently in focus is updated to tabindex="0". Focus "roves" through the tree in response to keypresses.
<li tabindex="-1">File 1</li>
<li tabindex="-1">File 2</li>
<li tabindex="0">File 3</li>
<li tabindex="-1">File 4</li>
GitHub evaluated aria-activedescendant as an alternative, but it had issues with VoiceOver on macOS and iOS, so the roving tabindex approach was preferred.
Enhancing the semantics with ARIA
The semantic foundation provides structure, but ARIA is still required to communicate tree-specific semantics. The implementation follows APG recommendations for roles and properties:
role="tree"on the rootulelementrole="treeitem"on eachlielementrole="group"on nestedulelements to group child nodes under their parentaria-expandedon branch nodes, set totrueorfalsedepending on statearia-selectedto mark the node that holds the active selection for keyboard interaction
The pattern here is not a case of "ARIA is bad" — these roles are necessary because HTML lacks native tree semantics.
Two additional declarations were added beyond the APG guidance:
aria-hidden="true"on SVG icons (such as folder and file glyphs) so their contents are not announcedaria-current="true"on the selected node, which helps when a tree node is deep-linked via URL fragment
Keyboard navigation expectations
Operating a tree view feels different from operating a typical list of links. People expect directional arrow keys to move selection without leaving the tree. File Explorer's model establishes a set of behaviors that GitHub mirrors:
- Tab — Moves focus into and out of the tree; one tab stop for the whole component.
- Enter — On a branch: opens it. On a leaf: opens its target.
- Down / Up — Moves selection to the next or previous visible node without expanding or collapsing anything.
- Right — On a collapsed branch: expands it and keeps selection. On an expanded branch: moves into its first child.
- Left — On an expanded branch: collapses it. On a collapsed branch or a leaf: moves focus to the parent.
- Home / End — Jumps selection to the first or last node in the tree.
The implementation also supports typeahead: if a user types characters while the tree is focused, selection moves to the node closest to the current selection whose name matches the typed text. This mirrors Windows File Explorer behavior.
Because tree view nodes are not anchor elements, they don't natively support the actions users associate with links — like opening in a new tab or middle-clicking to open a new window. GitHub listens for middle clicks and Ctrl+Enter keypresses specifically to replicate these behaviors for tree items that represent files.
Handling loading and error states
Tree views in GitHub sometimes need to fetch content after a branch is expanded — and unlike a static file listing, the amount of content may not be known up front. When a branch is expanded and a placeholder is shown while content loads, accessibility is maintained in two ways.

First, live region announcements tell the screen reader what is happening. The text differs based on what is known:
- When the number of nodes is known, the announcement reads "Loading {x} items."
- When the number is unknown, it simply reads "Loading…"
- When nothing loads, it reads "{branch node name} is empty."
Second, focus is moved in a way that keeps the user's place in the tree predictable:
- If focus is on a loading placeholder and content arrives, focus moves to the first of the newly loaded nodes.
- If a branch contains no content, focus goes back to the branch node, and its
aria-expandedvalue is removed.
If fetching content fails — due to network degradation or a system outage — the error is surfaced through a standard dialog component, since anything else would create untested and likely unreliable behavior.
Closing the assistive technology gap
Complex interaction patterns are prone to interoperability issues, so it's critical to validate the implementation with real assistive technology. Two adjustments proved necessary in our testing:
Declare depth with aria-level
Screen readers naturally report the nesting depth of standard list items. A li inside a ul nested three levels deep gets announced accordingly. To reproduce this behavior for tree view nodes, we had to set aria-level="3" explicitly on each li element. This resolved correct depth announcements across multiple assistive technologies we tested.
Pin the accessible name to the li
A node's accessible name is usually derived from the text directly inside the li:
<li>README.md</li>
VoiceOver on macOS and iOS did not honor this, likely due to the complexity of each node's internal DOM. We solved it with aria-labelledby, pointing to the id on the text element:
<li aria-labelledby="readme-md">
<div>
<!-- Icon -->
</div>
<div id="readme-md">
README.md
</div>
</li>
This ensures the accessible name is announced when the li receives focus and that the announcement matches what is visibly rendered.
Next steps in the component's evolution
We are actively prototyping two extensions to better serve GitHub's users:
Native link behavior inside nodes
Browsers provide built-in behaviors for anchors, like copying the URL. We want to replace our JavaScript-based middle-click handler with a native solution that does not compromise assistive technology support or interoperability.
Multiple actions per node
The tree view pattern assumes navigation and activation are the only user intents. GitHub workflows demand more actions per node, and we're exploring ways to support that. This is an opportunity to push the web's tree view construct forward.
Lessons from the process
Building an accessible tree view requires sustained testing and iteration. The effort is justified: it ensures a core GitHub surface works for everyone, regardless of device or ability. We're sharing these considerations in hopes they help others working on similar components. If you encounter issues with our tree view and assistive technology, please let us know — your reports directly shape our accessibility work.



