-
Notifications
You must be signed in to change notification settings - Fork 131
chore(treeview): add treeitem role to shadow dom node #3894
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🦋 Changeset detectedLatest commit: 4947038 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR attempts to fix an accessibility audit issue by adding role="treeitem" to the tree-view-include-fragment custom element in the spinner loader component. However, this creates an invalid ARIA tree structure.
Changes:
- Added
role="treeitem"attribute to thetree-view-include-fragmentelement inspinner_loader.html.erb
Comments suppressed due to low confidence (1)
app/components/primer/alpha/tree_view/spinner_loader.html.erb:5
- The
skeleton_loader.html.erbfile does not haverole="treeitem"on itstree-view-include-fragmentelement (line 5), creating an inconsistency with the spinner_loader change. This inconsistency suggests that adding the role to spinner_loader may not be the correct approach. Both loaders should be treated the same way since they serve the same purpose and have the same DOM structure.
<%= render(Primer::BaseComponent.new(tag: :"tree-view-include-fragment", src: @src, loading: :lazy, data: { target: "tree-view-sub-tree-node.subTree tree-view-sub-tree-node.includeFragment", path: @container.path.to_json }, hidden: @container.expanded?, accept: "text/fragment+html", role: "treeitem")) do %>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <% end %> | ||
|
|
||
| <%= render(Primer::BaseComponent.new(tag: :"tree-view-include-fragment", src: @src, loading: :lazy, data: { target: "tree-view-sub-tree-node.subTree tree-view-sub-tree-node.includeFragment", path: @container.path.to_json }, hidden: @container.expanded?, accept: "text/fragment+html")) do %> | ||
| <%= render(Primer::BaseComponent.new(tag: :"tree-view-include-fragment", src: @src, loading: :lazy, data: { target: "tree-view-sub-tree-node.subTree tree-view-sub-tree-node.includeFragment", path: @container.path.to_json }, hidden: @container.expanded?, accept: "text/fragment+html", role: "treeitem")) do %> |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding role="treeitem" to the tree-view-include-fragment element creates an invalid ARIA tree structure. According to the WAI-ARIA specification, a treeitem element cannot contain a group element, but this tree-view-include-fragment wraps a ul[role="group"] (SubTreeContainer).
The actual DOM structure becomes:
li[role="none"]
└── tree-view-include-fragment[role="treeitem"] ← Invalid
└── ul[role="group"]
└── li[role="none"]
└── div[role="treeitem"]
The tree-view-include-fragment is a technical implementation detail for lazy loading and should remain role-less (allowing it to be transparent in the accessibility tree). The actual treeitems are the div[role="treeitem"] elements inside the ul[role="group"] that this fragment contains.
Note that skeleton_loader.html.erb does not have this role attribute, creating an inconsistency. The correct fix depends on what the actual accessibility issue is. If the concern is that the loading indicator needs to be announced, the role should be on the inner Node element (which already has role="treeitem" from Node.rb line 108), not on the outer include-fragment wrapper.
| <%= render(Primer::BaseComponent.new(tag: :"tree-view-include-fragment", src: @src, loading: :lazy, data: { target: "tree-view-sub-tree-node.subTree tree-view-sub-tree-node.includeFragment", path: @container.path.to_json }, hidden: @container.expanded?, accept: "text/fragment+html", role: "treeitem")) do %> | |
| <%= render(Primer::BaseComponent.new(tag: :"tree-view-include-fragment", src: @src, loading: :lazy, data: { target: "tree-view-sub-tree-node.subTree tree-view-sub-tree-node.includeFragment", path: @container.path.to_json }, hidden: @container.expanded?, accept: "text/fragment+html")) do %> |
…onents into chore/add-treeview-role
…onents into chore/add-treeview-role
…onents into chore/add-treeview-role
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (3)
app/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.ts:432
- Potential null reference error: this code checks attributes on
this.subTreewithout first verifying thatthis.subTreeexists. Line 425 shows thatthis.subTreecan be null/undefined. This check should be inside theif (this.subTree)block on line 425 to prevent accessing attributes on a null/undefined object.
#update() {
if (this.expanded) {
if (this.subTree) this.subTree.hidden = false
if (this.subTree.getAttribute('data-target')?.includes('tree-view-sub-tree-node.includeFragment')) {
this.subTree.setAttribute('role', 'treeitem')
// Ensure the include-fragment can participate in roving tab index
app/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.ts:450
- Potential null reference error: this code checks attributes on
this.subTreewithout first verifying thatthis.subTreeexists. Line 445 shows thatthis.subTreecan be null/undefined. This check should be inside theif (this.subTree)block on line 445 to prevent accessing attributes on a null/undefined object.
this.collapsedToggleIcon.setAttribute('hidden', 'hidden')
}
} else {
if (this.subTree) this.subTree.hidden = true
if (this.subTree.getAttribute('data-target')?.includes('tree-view-sub-tree-node.includeFragment')) {
app/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.ts:432
- The approach of checking if
this.subTreeis an include-fragment by string matching on the data-target attribute is fragile and could break if the attribute format changes. Consider using a more robust check likethis.subTree instanceof TreeViewIncludeFragmentElementorthis.subTree === this.includeFragmentto verify if the subTree element is an include-fragment.
#update() {
if (this.expanded) {
if (this.subTree) this.subTree.hidden = false
if (this.subTree.getAttribute('data-target')?.includes('tree-view-sub-tree-node.includeFragment')) {
this.subTree.setAttribute('role', 'treeitem')
// Ensure the include-fragment can participate in roving tab index
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
app/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.ts
Outdated
Show resolved
Hide resolved
app/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.ts
Show resolved
Hide resolved
app/components/primer/alpha/tree_view/tree_view_sub_tree_node_element.ts
Show resolved
Hide resolved
|
@francinelucca I've opened a new pull request, #3917, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@francinelucca I've opened a new pull request, #3918, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: francinelucca <[email protected]>
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: francinelucca <[email protected]>
Add treeitem role to shadow DOM node in treeview.
Authors: Please fill out this form carefully and completely.
Reviewers: By approving this Pull Request you are approving the code change, as well as its deployment and mitigation plans.
Please read this description carefully. If you feel there is anything unclear or missing, please ask for updates.
Relates to https://github.com/github/accessibility-audits/issues/14650
What are you trying to accomplish?
Resolve accessibility audit issue where child role is missing
Accessibility and Focus Management Improvements:
include-fragmentwithrole="treeitem", it is assigned the correcttabindexandroleattributes when expanded, and these attributes are removed when collapsed. This allows the fragment to participate in keyboard navigationinclude-fragmentsubtree element itself is focused, itstabindexis managed correctly when moving focus between nodes.include-fragmentitself has focus during fragment replacement, ensuring focus state is preserved.Testing:
include-fragmentnode is collapsed.Screenshots
Integration
N/A
List the issues that this change affects.
Closes https://github.com/github/accessibility-audits/issues/14650
Risk Assessment
What approach did you choose and why?
Consulted a11y on the best fix and decided to add
role="treeitem"to the shadow dom node to solve the a11y scan issue, though this may not translate to a real world scenario in this case.Anything you want to highlight for special attention from reviewers?
N/A
Accessibility
Merge checklist
Take a look at the What we look for in reviews section of the contributing guidelines for more information on how we review PRs.