{"id":82266,"date":"2025-01-28T09:00:39","date_gmt":"2025-01-28T17:00:39","guid":{"rendered":"https:\/\/github.blog\/?p=82266"},"modified":"2025-01-27T14:08:27","modified_gmt":"2025-01-27T22:08:27","slug":"considerations-for-making-a-tree-view-component-accessible","status":"publish","type":"post","link":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/","title":{"rendered":"Considerations for making a tree view component accessible"},"content":{"rendered":"<!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD HTML 4.0 Transitional\/\/EN\" \"http:\/\/www.w3.org\/TR\/REC-html40\/loose.dtd\">\n<html><body><p>Tree views are a core part of the GitHub experience. You&rsquo;ve encountered one if you&rsquo;ve ever navigated through a repository&rsquo;s file structure or reviewed a pull request.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/github-repo-browser.png?w=1024&#038;resize=1024%2C653\" alt=\"Browsing files on Primer's design repository. A tree view showing the repositories directory structure occupies a quarter of the screen. The other three quarters are taken up by the content of the content subdirectory. The tree view shows expanded and collapsed directories, as well as files nested at multiple levels of depth.\" width=\"1024\" height=\"653\" class=\"alignnone size-large wp-image-82267 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/github-repo-browser.png?w=2544 2544w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/github-repo-browser.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/github-repo-browser.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/github-repo-browser.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/github-repo-browser.png?w=1536 1536w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/github-repo-browser.png?w=2048 2048w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>On GitHub, a tree view is the list of folders and the files they contain. It is analogous to the directory structure your operating system uses as a way of organizing things.<\/p>\n<p>Tree views are notoriously difficult to implement in an accessible way. This post is a deep dive into some of the major considerations that went into how we made <a href=\"https:\/\/primer.style\/components\/tree-view\">GitHub&rsquo;s tree view component<\/a> accessible. We hope that it can be used as a reference and help others.<\/p>\n<h2 id=\"start-with-windows\" id=\"start-with-windows\" ><a class=\"heading-link\" href=\"#start-with-windows\">Start with Windows<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>It&rsquo;s important to have components with complex interaction requirements <strong>map to something people are already familiar with using<\/strong>. This allows for responsiveness to the keypresses they will try to navigate and take action on our tree view instances.<\/p>\n<p>We elected to adopt Windows File Explorer&rsquo;s tree view implementation, given <a href=\"https:\/\/webaim.org\/projects\/screenreadersurvey9\/#os\">the prominence of Windows&rsquo; usage<\/a> for desktop screen reader users.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/windows-file-explorer.png?w=1024&#038;resize=1024%2C783\" alt=\"A Windows 11 File Explorer window showing a tree view and a list of subdirectories that one of its folders contains. The tree view demonstrates how the C drive contains multiple nested folders to organize its content.\" width=\"1024\" height=\"783\" class=\"alignnone size-large wp-image-82268 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/windows-file-explorer.png?w=1744 1744w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/windows-file-explorer.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/windows-file-explorer.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/windows-file-explorer.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/windows-file-explorer.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>Navigating and taking actions on items in Windows&rsquo; tree view using <a href=\"https:\/\/www.nvaccess.org\/\">NVDA<\/a> and <a href=\"https:\/\/www.freedomscientific.com\/products\/software\/jaws\/\">JAWS<\/a> helped us get a better understanding of how things worked, including factors such as focus management, keyboard shortcuts, and expected assistive technology announcements.<\/p>\n<h2 id=\"then-maybe-reference-the-apg\" id=\"then-maybe-reference-the-apg\" ><a class=\"heading-link\" href=\"#then-maybe-reference-the-apg\">Then maybe reference the APG<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>The <a href=\"https:\/\/www.w3.org\/WAI\/ARIA\/apg\/\">ARIA Authoring Practices Guide (APG)<\/a> is a bit of an odd artifact. It looks official but is no <a href=\"https:\/\/lists.w3.org\/Archives\/Public\/public-wai-announce\/2022AprJun\/0003.html\">longer recognized by the W3C as a formal document<\/a>.<\/p>\n<p>This is to say that the APG can serve as a helpful high-level resource for things to consider for your overall approach, but its suggestions for code necessitate deeper scrutiny.<\/p>\n<h2 id=\"build-from-a-solid-semantic-foundation\" id=\"build-from-a-solid-semantic-foundation\" ><a class=\"heading-link\" href=\"#build-from-a-solid-semantic-foundation\">Build from a solid, semantic foundation<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>At its core, a tree view is a list of lists. Because of this, we used <code>ul<\/code> and <code>li<\/code> elements for parent and child nodes:<\/p>\n<pre><code>&lt;ul&gt;\n  &lt;li&gt;\n    &lt;ul&gt;\n      &lt;li&gt;.github\/&lt;\/li&gt;\n      &lt;li&gt;source\/&lt;\/li&gt;\n      &lt;li&gt;test\/&lt;\/li&gt;\n    &lt;\/ul&gt;\n  &lt;\/li&gt;\n  &lt;li&gt;.gitignore&lt;\/li&gt;\n  &lt;li&gt;README.md&lt;\/li&gt;\n&lt;\/ul&gt;\n<\/code><\/pre>\n<p>There are a few reasons for doing this, but the main considerations are:<\/p>\n<ul>\n<li>Better assurance that <a href=\"https:\/\/alistapart.com\/article\/semantics-to-screen-readers\/\">a meaningful accessibility tree is generated<\/a>,  <\/li>\n<li>Lessening the work we need for future maintenance, and consequential re-verification that our updates continue to work properly, and  <\/li>\n<li>Better guaranteed interoperability between different browsers, apps, and other technologies.<\/li>\n<\/ul>\n<p><strong>NOTE<\/strong>: GitHub currently does not virtualize its file trees. We would need to revisit this architectural decision if this ever changes.<\/p>\n<h3 id=\"better-broad-assistive-technology-support\" id=\"better-broad-assistive-technology-support\" ><a class=\"heading-link\" href=\"#better-broad-assistive-technology-support\">Better broad assistive technology support<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>The more complicated an interactive pattern is, the greater the risk that there are <a href=\"https:\/\/www.a11yproject.com\/posts\/aria-has-perfect-support\/#compatibility-issues\">bugs or gaps with assistive technology support<\/a>.<\/p>\n<p>Given the size of the audience GitHub serves, it&rsquo;s important that we consider more than just <a href=\"https:\/\/webaim.org\/projects\/screenreadersurvey10\/#primary\">majority share assistive technology<\/a> considerations.<\/p>\n<p>We found that utilizing semantic HTML elements also performed better for some less-common assistive technologies. This was especially relevant with some lower-power devices, like an entry-level Android smartphone from 2021.<\/p>\n<h3 id=\"better-forced-color-mode-support\" id=\"better-forced-color-mode-support\" ><a class=\"heading-link\" href=\"#better-forced-color-mode-support\">Better Forced Color Mode support<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>Semantic HTML elements also map to native operating system UI patterns, meaning that <a href=\"https:\/\/blogs.windows.com\/msedgedev\/2020\/09\/17\/styling-for-windows-high-contrast-with-new-standards-for-forced-colors\/\">Forced Color Mode<\/a>&rsquo;s heuristics will recognize them without any additional effort. This is helpful for people who rely on the mode to see screen content.<\/p>\n<p>The heuristic mapping behavior does not occur if we used semantically neutral <code>div<\/code> or <code>span<\/code> elements, and would have to be manually recreated and maintained.<\/p>\n<h2 id=\"use-a-composite-widget\" id=\"use-a-composite-widget\" ><a class=\"heading-link\" href=\"#use-a-composite-widget\">Use a composite widget<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>A <a href=\"https:\/\/w3c.github.io\/aria\/#composite\">composite widget<\/a> allows a component that contains multiple interactive elements to only require one tab stop unless someone chooses to interact with it further.<\/p>\n<p>Consider a file tree for a repository that contains 500+ files in 20+ directories. Without a composite widget treatment, someone may have to press <kbd>Tab<\/kbd> far too many times to bypass the file tree component and get what they need.<\/p>\n<h2 id=\"think-about-wrapping-it-in-a-landmark\" id=\"think-about-wrapping-it-in-a-landmark\" ><a class=\"heading-link\" href=\"#think-about-wrapping-it-in-a-landmark\">Think about wrapping it in a landmark<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>Like using a composite widget, <a href=\"https:\/\/www.w3.org\/WAI\/ARIA\/apg\/practices\/landmark-regions\/\">landmark regions<\/a> help some people quickly and efficiently navigate through larger overall sections of the page. Because of this, we wrapped the entire file tree in a <a href=\"https:\/\/www.w3.org\/WAI\/ARIA\/apg\/practices\/landmark-regions\/\"><code>nav<\/code> landmark element<\/a>.<\/p>\n<p>This does not mean every tree view component should be a landmark, however! We made this decision for the file tree because it is frequently interacted with as <a href=\"https:\/\/www.w3.org\/WAI\/WCAG22\/Understanding\/multiple-ways.html\">a way to navigate through a repository&rsquo;s content<\/a>.<\/p>\n<h2 id=\"go-with-a-roving-tabindex-approach\" id=\"go-with-a-roving-tabindex-approach\" ><a class=\"heading-link\" href=\"#go-with-a-roving-tabindex-approach\">Go with a roving <code>tabindex<\/code> approach<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>A <a href=\"https:\/\/www.w3.org\/WAI\/ARIA\/apg\/practices\/keyboard-interface\/#kbd_roving_tabindex\">roving tabindex<\/a> is a technique that uses <code>tabindex=\"-1\"<\/code> applied to each element in a series, and then updates the <code>tabindex<\/code> value to use <code>0<\/code> instead in response to user keyboard input. This allows someone to traverse the series of elements, as focus &ldquo;roves&rdquo; to follow their keypresses.<\/p>\n<pre><code>&lt;li tabindex=\"-1\"&gt;File 1&lt;\/li&gt;\n&lt;li tabindex=\"-1\"&gt;File 2&lt;\/li&gt;\n&lt;li tabindex=\"0\"&gt;File 3&lt;\/li&gt;\n&lt;li tabindex=\"-1\"&gt;File 4&lt;\/li&gt;\n<\/code><\/pre>\n<p>The roving <code>tabindex<\/code> approach performed better than <a href=\"https:\/\/www.w3.org\/WAI\/ARIA\/apg\/practices\/keyboard-interface\/#kbd_focus_activedescendant\">utilizing <code>aria-activedescendant<\/code><\/a>, which had issues with VoiceOver on macOS and iOS.<\/p>\n<h2 id=\"enhance-with-aria\" id=\"enhance-with-aria\" ><a class=\"heading-link\" href=\"#enhance-with-aria\">Enhance with ARIA<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>We use a considered set of <a href=\"https:\/\/w3c.github.io\/aria\/\">ARIA<\/a> declarations to build off our semantic foundation.<\/p>\n<p>Note that while we intentionally started with semantic HTML, there are certain ARIA declarations that are needed. The use of ARIA here is necessary and intentional, as it expands the capabilities of HTML to describe something that HTML alone cannot describe&mdash;a tree view construct.<\/p>\n<p>Our overall approach follows <a href=\"https:\/\/www.w3.org\/WAI\/ARIA\/apg\/patterns\/treeview\/#wai-ariaroles,states,andproperties\">what the APG suggests<\/a>, in that we use the following:<\/p>\n<ul>\n<li><code>role=\"tree\"<\/code> is placed on the parent <code>ul<\/code> element, to communicate that it is a tree view construct.  <\/li>\n<li><code>role=\"treeitem\"<\/code> is placed on the child <code>li<\/code> elements, to communicate that they are tree view nodes.  <\/li>\n<li><code>role=\"group\"<\/code> is declared on child <code>ul<\/code> elements, to communicate that they contain branch and leaf nodes.  <\/li>\n<li><code>aria-expanded<\/code> is declared on directories, with a value of <code>true<\/code> to communicate that the branch node is in an opened state and a value of <code>false<\/code> to communicate that it is in a collapsed state instead.  <\/li>\n<li><code>aria-selected<\/code> is used to indicate if branch or leaf nodes have been chosen by user navigation, and can therefore have user actions applied to them.<\/li>\n<\/ul>\n<p>We also made the following additions:<\/p>\n<ul>\n<li><code>aria-hidden=\"true\"<\/code> is applied to SVG icons (folders, files, etc.) to ensure its content is not announced.  <\/li>\n<li><code>aria-current=\"true\"<\/code> is placed on the selected node to better support when a node is deep linked to via URL.<\/li>\n<\/ul>\n<p><strong>NOTE<\/strong>: We use &ldquo;branch node&rdquo; and &ldquo;leaf node&rdquo; as broad terms that can apply to all tree view components we use on GitHub. For the file tree, branch nodes would correspond to directories and subdirectories, and leaf nodes would correspond to files.<\/p>\n<h2 id=\"support-expected-navigation-techniques\" id=\"support-expected-navigation-techniques\" ><a class=\"heading-link\" href=\"#support-expected-navigation-techniques\">Support expected navigation techniques<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>The following behaviors are what people will try when operating a tree view construct, so we support them:<\/p>\n<h3 id=\"keyboard-keypresses\" id=\"keyboard-keypresses\" ><a class=\"heading-link\" href=\"#keyboard-keypresses\">Keyboard keypresses<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<ul>\n<li><kbd>Tab<\/kbd>: Places focus on the entire tree view component, then moves focus to the next focusable item on the view.  <\/li>\n<li><kbd>Enter<\/kbd>:\n<ul>\n<li>If a branch node is selected: Displays the directory&rsquo;s contents.  <\/li>\n<li>If a leaf node is selected: Displays the leaf node&rsquo;s contents.  <\/li>\n<\/ul>\n<\/li>\n<li><kbd>Down<\/kbd>: Moves selection to the next node that can be selected without opening or closing a node.  <\/li>\n<li><kbd>Up<\/kbd>: Moves selection to the previous node that can be selected without opening or closing a node.  <\/li>\n<li><kbd>Right<\/kbd>:\n<ul>\n<li>If a branch node is selected and in a collapsed state: Expands the selected collapsed branch node and does not move selection.  <\/li>\n<li>If a branch node is selected and in an expanded state: Moves selection to the directory&rsquo;s first child node.  <\/li>\n<\/ul>\n<\/li>\n<li><kbd>Left<\/kbd>:\n<ul>\n<li>If a branch node is selected and in an expanded state: Collapses the selected collapsed directory node and does not move selection.  <\/li>\n<li>If a branch node is selected and in a collapsed state: Moves selection to the node&rsquo;s parent directory.  <\/li>\n<li>If a leaf node is selected: Moves selection to the leaf node&rsquo;s parent directory.  <\/li>\n<\/ul>\n<\/li>\n<li><kbd>End<\/kbd>: Moves selection to the last node that can be selected.  <\/li>\n<li><kbd>Home<\/kbd>: Moves selection to the first node that can be selected.<\/li>\n<\/ul>\n<p>We also support <strong>typeahead selection<\/strong>, as we are modeling Windows File Explorer&rsquo;s tree view behaviors. Here, we move selection to the node closest to the currently selected node whose name matches what the user types.<\/p>\n<h3 id=\"middle-clicking\" id=\"middle-clicking\" ><a class=\"heading-link\" href=\"#middle-clicking\">Middle clicking<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>Nodes on tree view constructs are <a href=\"https:\/\/w3c.github.io\/aria\/#treeitem\">tree items<\/a>, not links. Because of this, tree view nodes do not support the behaviors you get with using an anchor element, such as opening its URL in a new tab or window.<\/p>\n<p>We use JavaScript to listen for middle clicks and <kbd>Control<\/kbd>+<kbd>Enter<\/kbd> keypresses to replicate this behavior.<\/p>\n<h2 id=\"consider-states\" id=\"consider-states\" ><a class=\"heading-link\" href=\"#consider-states\">Consider states<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<h3 id=\"loading\" id=\"loading\" ><a class=\"heading-link\" href=\"#loading\">Loading<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>Tree views on GitHub can take time to retrieve their content, and we may not always know how much content a branch node contains.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/tree-view-loading-nodes.png?w=890&#038;resize=890%2C282\" alt=\"A directory called, 'src' that is selected and in an expanded that. It contains a single leaf node that contains a loading spinner with a label of 'Loading&hellip;&quot;.\" width=\"890\" height=\"282\" class=\"alignnone size-large wp-image-82277 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/tree-view-loading-nodes.png?w=890 890w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/tree-view-loading-nodes.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2025\/01\/tree-view-loading-nodes.png?w=768 768w\" sizes=\"auto, (max-width: 890px) 100vw, 890px\" \/><\/p>\n<p><a href=\"https:\/\/www.w3.org\/TR\/wai-aria-1.3\/#dfn-live-region\">Live region<\/a> announcements are <a href=\"https:\/\/tetralogical.com\/blog\/2024\/05\/01\/why-are-my-live-regions-not-working\/\">tricky to get right<\/a>, but integral to creating an equivalent experience. We use the following announcements:<\/p>\n<ul>\n<li>If there is a known amount of nodes that load, we enumerate the incoming content with an announcement that reads, &ldquo;Loading {x} items.&rdquo;  <\/li>\n<li>If there is an unknown number of nodes that load, we instead use a more generic announcement of, &ldquo;Loading&hellip;&rdquo;  <\/li>\n<li>If there are no nodes that load we use an announcement message that reads, &ldquo;{branch node name} is empty.&rdquo;<\/li>\n<\/ul>\n<p>Additionally, we <a href=\"https:\/\/primer.style\/guides\/accessibility\/focus-management\">manage focus<\/a> for loading content:<\/p>\n<ul>\n<li>If focus is placed on a placeholder loading node when the content loads in: Move focus from the placeholder node to the first child node in the branch node.   <\/li>\n<li>If focus is on a placeholder loading node but the branch node does not contain content: Move focus back to the branch node. Additionally, we remove the branch node&rsquo;s <code>aria-expanded<\/code> declaration.<\/li>\n<\/ul>\n<h3 id=\"errors\" id=\"errors\" ><a class=\"heading-link\" href=\"#errors\">Errors<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>Circumstances can conspire to interfere with a tree view component&rsquo;s intended behavior. Examples of this could be a branch node failing to retrieve content or a <a href=\"https:\/\/primer.style\/ui-patterns\/degraded-experiences\">partial system outage<\/a>.<\/p>\n<p>In these scenarios, the tree view component will use a straightforward <a href=\"https:\/\/primer.style\/components\/dialog\">dialog component<\/a> to communicate the error.<\/p>\n<h2 id=\"fix-interoperability-issues\" id=\"fix-interoperability-issues\" ><a class=\"heading-link\" href=\"#fix-interoperability-issues\">Fix interoperability issues<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>As previously touched on, complicated interaction patterns run the risk of compatibility issues. Because of this, it&rsquo;s essential to test your efforts with actual assistive technology to <strong>ensure it actually works<\/strong>.<\/p>\n<p>We made the following adjustments to provide better assistive technology support:<\/p>\n<h3 id=\"use-aria-level\" id=\"use-aria-level\" ><a class=\"heading-link\" href=\"#use-aria-level\">Use <code>aria-level<\/code><span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>Screen readers can report on the depth of a nested list item. For example, a <code>li<\/code> element placed inside of a <code>ul<\/code> element nested three levels deep can announce itself as such.<\/p>\n<p>We found that we needed to explicitly declare the level on each <code>li<\/code> element to recreate this behavior for a tree view. For our example, we&rsquo;d also need to set <code>aria-level=\"3\"<\/code> on the <code>li<\/code> element.<\/p>\n<p>This fix addressed multiple forms of assistive technology we tested with.<\/p>\n<h3 id=\"explicitly-set-the-nodes-accessible-name-on-the-li-element\" id=\"explicitly-set-the-nodes-accessible-name-on-the-li-element\" ><a class=\"heading-link\" href=\"#explicitly-set-the-nodes-accessible-name-on-the-li-element\">Explicitly set the node&rsquo;s accessible name on the <code>li<\/code> element<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>A node&rsquo;s <a href=\"https:\/\/sarahmhigley.com\/writing\/whats-in-a-name\/\">accessible name<\/a> is typically set by the text string placed inside the <code>li<\/code> element:<\/p>\n<pre><code>&lt;li&gt;README.md&lt;\/li&gt;\n<\/code><\/pre>\n<p>However, we found that <a href=\"https:\/\/support.apple.com\/guide\/voiceover\/get-started-vo4be8816d70\/10\/mac\/15.0\">VoiceOver on macOS<\/a> and <a href=\"https:\/\/support.apple.com\/guide\/iphone\/turn-on-and-practice-voiceover-iph3e2e415f\/ios\">iOS<\/a> did not support this. This may be because of the relative complexity of each node&rsquo;s inner DOM structure.<\/p>\n<p>We used <code>aria-labelledby<\/code> to get around this problem, with a value that pointed to the <code>id<\/code> set on the text portion of each node:<\/p>\n<pre><code>&lt;li aria-labelledby=\"readme-md\"&gt;\n  &lt;div&gt;\n   &lt;!-- Icon --&gt;\n  &lt;\/div&gt;\n  &lt;div id=\"readme-md\"&gt;\n    README.md\n  &lt;\/div&gt;\n&lt;\/li&gt;\n<\/code><\/pre>\n<p>This guarantees that:<\/p>\n<ul>\n<li>the node&rsquo;s accessible name is announced when focus is placed on the <code>li<\/code> element,   <\/li>\n<li>and that the announcement matches what is shown visually.<\/li>\n<\/ul>\n<h2 id=\"where-wed-like-to-go-from-here\" id=\"where-wed-like-to-go-from-here\" ><a class=\"heading-link\" href=\"#where-wed-like-to-go-from-here\">Where we&rsquo;d like to go from here<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>There&rsquo;s a couple areas we&rsquo;re prototyping and iterating on to better serve our users:<\/p>\n<h3 id=\"supporting-links-inside-a-node\" id=\"supporting-links-inside-a-node\" ><a class=\"heading-link\" href=\"#supporting-links-inside-a-node\">Supporting links inside a node<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>Browsers apply a lot of behaviors to anchor elements, such as the ability to copy the URL.<\/p>\n<p>We&rsquo;d like to replace the JavaScript that listens for middle clicks with a more robust native solution, only without sacrificing interoperability and assistive technology support.<\/p>\n<h3 id=\"supporting-multiple-actions-per-node\" id=\"supporting-multiple-actions-per-node\" ><a class=\"heading-link\" href=\"#supporting-multiple-actions-per-node\">Supporting multiple actions per node<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>Tree views constructs were designed assuming a user will only ever navigate to a node and activate it.<\/p>\n<p>GitHub has use cases that require actions other than activating the node, and we&rsquo;re exploring how to accomplish that. This is exciting, as it represents an opportunity to evolve the tree view construct on the web.<\/p>\n<h2 id=\"always-learning\" id=\"always-learning\" ><a class=\"heading-link\" href=\"#always-learning\">Always learning<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>An accessible tree view is a complicated component to make, and it requires a lot of effort and testing to get right. However, this work helps to ensure that everyone can use a core part of GitHub, regardless of device, circumstance, or ability.<\/p>\n<p>We hope that highlighting the considerations that went into our work can help you on your accessibility journey.<br>\n<div class=\"post-content-cta\"><p><b>Share your experience<\/b>: <a href=\"https:\/\/github.com\/orgs\/community\/discussions\/categories\/accessibility\">We&rsquo;d love to hear from you<\/a> if you&rsquo;ve run into issues using our tree view component with assistive technology. This feedback is invaluable to helping us continue to make GitHub more accessible.<\/p>\n<\/div><\/p>\n<\/body><\/html>\n","protected":false},"excerpt":{"rendered":"<p>A deep dive on the work that went into making the component that powers repository and pull request file trees.<\/p>\n","protected":false},"author":2233,"featured_media":76531,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_gh_post_show_toc":"yes","_gh_post_is_no_robots":"no","_gh_post_is_featured":"yes","_gh_post_is_excluded":"no","_gh_post_is_unlisted":"no","_gh_post_related_link_1":"","_gh_post_related_link_2":"","_gh_post_related_link_3":"","_gh_post_sq_img":"","_gh_post_sq_img_id":"","_gh_post_cta_title":"","_gh_post_cta_text":"","_gh_post_cta_link":"","_gh_post_cta_button":"Click Here to Learn More","_gh_post_recirc_hide":"no","_gh_post_recirc_col_1":"78957","_gh_post_recirc_col_2":"78959","_gh_post_recirc_col_3":"78961","_gh_post_recirc_col_4":"65316","_featured_video":"","_gh_post_additional_query_params":"","_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_wpas_customize_per_network":false,"_links_to":"","_links_to_target":""},"categories":[72,3312],"tags":[2732,3505,3504,3507,3502,3051,3508,3503,3506],"coauthors":[3276],"class_list":["post-82266","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","category-user-experience","tag-accessibility","tag-apg","tag-aria","tag-assistive-technology","tag-components","tag-primer","tag-screen-readers","tag-tree-view","tag-wcag"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.3 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Considerations for making a tree view component accessible - The GitHub Blog<\/title>\n<meta name=\"description\" content=\"A deep dive on the work that went into making the component that powers repository and pull request file trees.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Considerations for making a tree view component accessible\" \/>\n<meta property=\"og:description\" content=\"A deep dive on the work that went into making the component that powers repository and pull request file trees.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/\" \/>\n<meta property=\"og:site_name\" content=\"The GitHub Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-01-28T17:00:39+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/02\/AI-DarkMode-3-1.png?fit=1200%2C630\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Eric Bailey\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Eric Bailey\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/\"},\"author\":{\"name\":\"Eric Bailey\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/f3c18020f3ba04d87d1ee7f1d816fe5d\"},\"headline\":\"Considerations for making a tree view component accessible\",\"datePublished\":\"2025-01-28T17:00:39+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/\"},\"wordCount\":2001,\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/02\\\/AI-DarkMode-3-1.png?fit=1200%2C630\",\"keywords\":[\"accessibility\",\"APG\",\"ARIA\",\"Assistive Technology\",\"Components\",\"Primer\",\"Screen Readers\",\"Tree View\",\"WCAG\"],\"articleSection\":[\"Engineering\",\"User experience\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/\",\"url\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/\",\"name\":\"Considerations for making a tree view component accessible - The GitHub Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/02\\\/AI-DarkMode-3-1.png?fit=1200%2C630\",\"datePublished\":\"2025-01-28T17:00:39+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/f3c18020f3ba04d87d1ee7f1d816fe5d\"},\"description\":\"A deep dive on the work that went into making the component that powers repository and pull request file trees.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/#primaryimage\",\"url\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/02\\\/AI-DarkMode-3-1.png?fit=1200%2C630\",\"contentUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/02\\\/AI-DarkMode-3-1.png?fit=1200%2C630\",\"width\":1200,\"height\":630},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/considerations-for-making-a-tree-view-component-accessible\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/github.blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Engineering\",\"item\":\"https:\\\/\\\/github.blog\\\/engineering\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"User experience\",\"item\":\"https:\\\/\\\/github.blog\\\/engineering\\\/user-experience\\\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Considerations for making a tree view component accessible\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/github.blog\\\/#website\",\"url\":\"https:\\\/\\\/github.blog\\\/\",\"name\":\"The GitHub Blog\",\"description\":\"Updates, ideas, and inspiration from GitHub to help developers build and design software.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/github.blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/f3c18020f3ba04d87d1ee7f1d816fe5d\",\"name\":\"Eric Bailey\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a8307137b2d25515ad72f1a335322a12b7d54dd7d77a7ad63dc7504e0b041733?s=96&d=mm&r=gcf815a27d5529eb562901f9e2a5da466\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a8307137b2d25515ad72f1a335322a12b7d54dd7d77a7ad63dc7504e0b041733?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/a8307137b2d25515ad72f1a335322a12b7d54dd7d77a7ad63dc7504e0b041733?s=96&d=mm&r=g\",\"caption\":\"Eric Bailey\"},\"url\":\"https:\\\/\\\/github.blog\\\/author\\\/ericwbailey\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Considerations for making a tree view component accessible - The GitHub Blog","description":"A deep dive on the work that went into making the component that powers repository and pull request file trees.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/","og_locale":"en_US","og_type":"article","og_title":"Considerations for making a tree view component accessible","og_description":"A deep dive on the work that went into making the component that powers repository and pull request file trees.","og_url":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/","og_site_name":"The GitHub Blog","article_published_time":"2025-01-28T17:00:39+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/github.blog\/wp-content\/uploads\/2024\/02\/AI-DarkMode-3-1.png?fit=1200%2C630","type":"image\/png"}],"author":"Eric Bailey","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Eric Bailey","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/#article","isPartOf":{"@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/"},"author":{"name":"Eric Bailey","@id":"https:\/\/github.blog\/#\/schema\/person\/f3c18020f3ba04d87d1ee7f1d816fe5d"},"headline":"Considerations for making a tree view component accessible","datePublished":"2025-01-28T17:00:39+00:00","mainEntityOfPage":{"@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/"},"wordCount":2001,"image":{"@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2024\/02\/AI-DarkMode-3-1.png?fit=1200%2C630","keywords":["accessibility","APG","ARIA","Assistive Technology","Components","Primer","Screen Readers","Tree View","WCAG"],"articleSection":["Engineering","User experience"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/","url":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/","name":"Considerations for making a tree view component accessible - The GitHub Blog","isPartOf":{"@id":"https:\/\/github.blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/#primaryimage"},"image":{"@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2024\/02\/AI-DarkMode-3-1.png?fit=1200%2C630","datePublished":"2025-01-28T17:00:39+00:00","author":{"@id":"https:\/\/github.blog\/#\/schema\/person\/f3c18020f3ba04d87d1ee7f1d816fe5d"},"description":"A deep dive on the work that went into making the component that powers repository and pull request file trees.","breadcrumb":{"@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/#primaryimage","url":"https:\/\/github.blog\/wp-content\/uploads\/2024\/02\/AI-DarkMode-3-1.png?fit=1200%2C630","contentUrl":"https:\/\/github.blog\/wp-content\/uploads\/2024\/02\/AI-DarkMode-3-1.png?fit=1200%2C630","width":1200,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/github.blog\/engineering\/user-experience\/considerations-for-making-a-tree-view-component-accessible\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/github.blog\/"},{"@type":"ListItem","position":2,"name":"Engineering","item":"https:\/\/github.blog\/engineering\/"},{"@type":"ListItem","position":3,"name":"User experience","item":"https:\/\/github.blog\/engineering\/user-experience\/"},{"@type":"ListItem","position":4,"name":"Considerations for making a tree view component accessible"}]},{"@type":"WebSite","@id":"https:\/\/github.blog\/#website","url":"https:\/\/github.blog\/","name":"The GitHub Blog","description":"Updates, ideas, and inspiration from GitHub to help developers build and design software.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/github.blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/github.blog\/#\/schema\/person\/f3c18020f3ba04d87d1ee7f1d816fe5d","name":"Eric Bailey","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/a8307137b2d25515ad72f1a335322a12b7d54dd7d77a7ad63dc7504e0b041733?s=96&d=mm&r=gcf815a27d5529eb562901f9e2a5da466","url":"https:\/\/secure.gravatar.com\/avatar\/a8307137b2d25515ad72f1a335322a12b7d54dd7d77a7ad63dc7504e0b041733?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a8307137b2d25515ad72f1a335322a12b7d54dd7d77a7ad63dc7504e0b041733?s=96&d=mm&r=g","caption":"Eric Bailey"},"url":"https:\/\/github.blog\/author\/ericwbailey\/"}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/github.blog\/wp-content\/uploads\/2024\/02\/AI-DarkMode-3-1.png?fit=1200%2C630","jetpack_shortlink":"https:\/\/wp.me\/pamS32-loS","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/82266","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/users\/2233"}],"replies":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/comments?post=82266"}],"version-history":[{"count":8,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/82266\/revisions"}],"predecessor-version":[{"id":82271,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/82266\/revisions\/82271"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media\/76531"}],"wp:attachment":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media?parent=82266"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/categories?post=82266"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/tags?post=82266"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/coauthors?post=82266"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}