Tree Views in React Aria and Angular CDK
Permalink to "Tree Views in React Aria and Angular CDK"Few teams hand-build tree widgets; most use a component library. Two of the most widely used options in the React and Angular ecosystems are React Aria Components’ Tree and the Angular CDK’s CdkTree. Both implement the core of the ARIA tree pattern, but they differ in how much they do for you, and both leave a few important pieces — announcements, empty and loading states, checkbox cascades — to your code.
This guide belongs to tree views & hierarchical navigation.
Spec reference
Permalink to "Spec reference"The requirements are those of the ARIA tree pattern, covered in the tree role for category and file browsers: a named tree, treeitems with aria-expanded on parents, levels and positions exposed, a single tab stop with roving focus, the arrow-key contract, and type-ahead. Relevant criteria are SC 1.3.1, 2.1.1 and 4.1.2 (A) and, for loading and cascades, SC 4.1.3 (AA).
When to use — and when not to
Permalink to "When to use — and when not to"Use React Aria’s Tree in React projects that want a complete, tested implementation including selection and type-ahead with little configuration. Use the Angular CDK tree in Angular projects; it gives structure and keyboard handling while leaving visuals and selection to you, which suits design systems.
Do not use either for data with columns — use a treegrid (React Aria Table with expandable rows, or an Angular treegrid implementation). Do not wrap them in extra clickable elements that change their semantics.
The misapplication: using an older CDK tree setup with cdkTreeNodePadding for indentation but no keyboard handling — older versions rendered roles but left arrow keys unimplemented. Check your version.
Annotated code example
Permalink to "Annotated code example"// React Aria Components
import { Tree, TreeItem, TreeItemContent, Collection, Button } from 'react-aria-components';
<Tree aria-label="Inventory categories" // named tree (SC 4.1.2)
items={categories} selectionMode="single"
expandedKeys={expanded} onExpandedChange={setExpanded}
onAction={(key) => openCategory(key)}>
{function renderItem(item) {
return (
<TreeItem id={item.id} textValue={item.name}> {/* name without icons */}
<TreeItemContent>
{({ hasChildItems }) => <>
{hasChildItems && <Button slot="chevron" aria-hidden="true">›</Button>}
<FolderIcon aria-hidden="true" />{item.name}
</>}
</TreeItemContent>
{item.children && <Collection items={item.children}>{renderItem}</Collection>}
</TreeItem>
);
}}
</Tree>
<!-- Angular CDK: nested tree -->
<cdk-tree [dataSource]="categories" [childrenAccessor]="children" aria-label="Inventory categories">
<cdk-nested-tree-node *cdkTreeNodeDef="let node" [isExpandable]="!!node.children?.length"
[attr.aria-selected]="node.id === selectedId" (click)="select(node)">
<span cdkTreeNodeToggle aria-hidden="true">›</span>{{ node.name }}
<div role="group" *ngIf="tree.isExpanded(node)"><ng-container cdkTreeNodeOutlet></ng-container></div>
</cdk-nested-tree-node>
</cdk-tree>
// Angular: announce lazy loads yourself
expand(node: Category) {
this.loading(node).subscribe(kids => this.announcer.announce(`${node.name}: ${kids.length} items`, 'polite'));
}
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Key | React Aria Tree | Angular CDK tree | Expected announcement |
|---|---|---|---|
| Up / Down | Built in | Built in (v17+) | Item name, level, position |
| Right / Left | Expand, child / collapse, parent | Built in | “expanded” / “collapsed” |
| Home / End | Built in | Built in | First / last item |
| Enter | onAction |
Your handler | Depends on action |
| Characters | Type-ahead | Check key manager | Matched item |
Integration context
Permalink to "Integration context"Whatever library you use, the expected behaviour is in the tree role for category and file browsers. For lazy children, add the messages from lazy-loading tree nodes with aria-busy. Treegrids in Angular are discussed in Angular CDK tree accessibility for nested data, and testing library components in testing library role queries for grids.
Gotchas
Permalink to "Gotchas"Chevron buttons. React Aria renders a chevron Button with slot="chevron"; it is excluded from the tab order by design. Do not add your own focusable expand buttons inside items.
Click handlers on node wrappers. In Angular, putting (click) on an inner div rather than the node can leave keyboard activation unimplemented. Handle Enter on the node.
Library upgrades. Tree keyboard behaviour has changed across major versions of both libraries. Re-test after upgrades.
Worked example
Permalink to "Worked example"A design system team wrapped React Aria’s Tree as CategoryTree for a retail platform. The wrapper required a label prop (used as aria-label), derived textValue from each category name, hid all icons, and connected lazy loading to the platform’s announcer. In parallel, the Angular back-office used the CDK tree with a similar wrapper. Both wrappers shared one set of message templates — “Loading {name}”, “{name}: {count} items”, “{name} is empty” — so a screen reader user moving between the storefront admin and the back office heard the same language in both.
Common mistakes
Permalink to "Common mistakes"- Unnamed trees — libraries cannot invent the name; pass one.
- Rich item content without a text value — badges and counts become part of the item’s name (“Phones 12 new 3 errors”).
- Assuming announcements exist — neither library announces lazy loads or cascades.
- Mixing a library tree with custom click handlers that bypass its keyboard handling.
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Wrap the library tree in a design-system component that enforces a name, sets text values from a label prop, hides icons, and integrates the shared announcer. Lock library versions and include a keyboard test in the component’s suite so upgrades cannot silently regress behaviour.
FAQ
Permalink to "FAQ"Is React Aria's Tree production-ready?
It is part of React Aria Components and widely used; check the release notes for your version and test with your target screen readers.
Does the Angular CDK tree handle keyboard navigation?
Recent versions (v17 and later) implement the tree keyboard pattern. Older versions required you to add it.
Can I use MUI X tree views instead?
Yes; MUI X SimpleTreeView and RichTreeView implement the pattern. The same configuration checks apply: names, text values, announcements.
Do these libraries support checkbox cascades?
React Aria supports multiple selection; tri-state cascading is logic you add. The CDK leaves selection entirely to you.
How do I test a library tree automatically?
Use role queries (getByRole(‘tree’), getAllByRole(‘treeitem’)) and keyboard events in Testing Library or Playwright, and assert aria-expanded and focus.
What about Vue?
Several Vue tree components exist with varying accessibility. Evaluate them against the keyboard contract and announcement needs before choosing.
Should I wrap the library tree or use it directly?
Wrap it. A thin design-system wrapper is where you enforce names, text values, icon hiding and announcements, so product teams cannot forget them.
Do these trees work with screen magnifiers?
Yes, provided focus indicators are clear and the focused node scrolls into view. Check that the library scrolls focused items into view in long trees.
Related
Permalink to "Related"- The tree role — the behaviour to verify
- Angular CDK tree for nested data — CDK trees with data
- Testing Library role queries for grids — testing components by role