Skip to content

Announce resource tree selection state to screen readers (#4798482)#2541

Open
jawelton74 wants to merge 4 commits into
masterfrom
users/jawelton/a11yfix-4798482-072026
Open

Announce resource tree selection state to screen readers (#4798482)#2541
jawelton74 wants to merge 4 commits into
masterfrom
users/jawelton/a11yfix-4798482-072026

Conversation

@jawelton74

@jawelton74 jawelton74 commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Preview this branch

Problem

In the Data Explorer left-navigation resource tree, screen readers (NVDA, JAWS, Narrator) announced tree items such as "Documents, 1 of 2, level 3" but never conveyed whether the item was selected or not selected. Screen-reader users could not tell which node represented their current context.

This failed MAS 4.1.2 – Name, Role, Value. The tree only reflected selection visually (via the selectedItem style class) with no corresponding accessible state.

Fix

Selection in the resource tree is a custom application-state concept (node.isSelected() combined with shouldShowAsSelected, which highlights only the deepest selected node). It intentionally does not use Fluent UI v9's built-in Tree selectionMode, because that renders visible radio buttons / checkboxes.

Fluent v9's TreeItem explicitly supports an aria-selected prop. This change wires the existing selection state to that prop on each TreeItem:

aria-selected={node.isSelected ? shouldShowAsSelected : undefined}

Behavior, following the ARIA single-select tree pattern:

  • Selectable nodes (those that define isSelected — databases, collections, Items/Documents, Settings/Scale, stored procedures, UDFs, triggers, conflicts, Home) announce aria-selected="true" when selected and "false" otherwise.
  • Pure grouping nodes (no isSelected — the Stored Procedures, User Defined Functions, Triggers, Schema folders and load more) omit the attribute entirely, so screen readers don't announce a misleading state for non-selectable containers.

Because it reuses shouldShowAsSelected, the announced state always mirrors the visual highlight (only the deepest selected node is "selected").

Expected screen-reader result

  • "Documents, selected, 1 of 2, level 3" when the item is selected
  • "Documents, not selected, 1 of 2, level 3" when it is not selected

Changes

  • src/Explorer/Controls/TreeComponent/TreeNodeComponent.tsx — add aria-selected to TreeItem.
  • src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx — add unit tests for selected, unselected, non-selectable (grouping), and selected-parent-with-selected-descendant cases.
  • src/Explorer/Controls/TreeComponent/__snapshots__/TreeNodeComponent.test.tsx.snap — updated snapshots.

Testing

  • Unit tests: npm run test:file -- src/Explorer/Controls/TreeComponent/TreeNodeComponent.test.tsx — 14 passed (4 new).

  • Lint / TypeScript / Prettier: eslint, tsc --noEmit, and prettier --check all pass on the changed files.

  • Manual E2E (dev server + a live SQL account): verified the rendered DOM and the platform accessibility tree.

    Node aria-selected
    Home / databases / containers true when selected, false otherwise
    container1/Items (bug scenario) true when selected, false otherwise
    Scale & Settings false when not selected
    Stored Procedures / UDFs / Triggers folders attribute omitted

    Selecting a leaf moved aria-selected="true" to that leaf and flipped its ancestor container back to "false", matching the visual highlight. Playwright's accessibility snapshot reported treeitem "Home" [selected], confirming the state reaches the platform accessibility API that NVDA consumes.

Risk

Low. The change is additive and scoped to a single accessibility attribute; it does not alter selection logic, styling, or navigation behavior.

The Data Explorer left-nav resource tree did not expose selection state to
assistive technology. Screen readers announced tree items (e.g. "Documents,
1 of 2, level 3") without conveying whether the item was selected, failing
MAS 4.1.2 (Name, Role, Value).

Selection in the tree is a custom app-state concept (node.isSelected /
shouldShowAsSelected) and does not use Fluent v9's built-in selectionMode.
Wire the existing selection state to the aria-selected prop on TreeItem so
selectable nodes announce "selected"/"not selected". Pure grouping nodes
(no isSelected) omit the attribute, matching the ARIA single-select tree
pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 956998dd-4849-4f7d-a027-b7190018e089
@jawelton74
jawelton74 requested a review from a team as a code owner July 20, 2026 22:24
@github-actions

Copy link
Copy Markdown

Playwright tests ✅ passed

Passed Failed Flaky Duration
653 0 10 844s

📁 Report: 29783836034-1/report.zip
Open container (Azure sign-in required) → click into 29783836034-1 folder → click report.zipDownload → unzip → open index.html · Workflow run

@github-actions

Copy link
Copy Markdown

Playwright tests ✅ passed

Passed Failed Flaky Duration
654 0 9 2857s

📁 Report: 29845371884-1/report.zip
Open container (Azure sign-in required) → click into 29845371884-1 folder → click report.zipDownload → unzip → open index.html · Workflow run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant