Skip to content

feat(treelist): full APG tree keyboard pattern + aria-level/posinset/setsize#3409

Merged
cixzhang merged 2 commits into
mainfrom
navi/feat/treelist-apg-keyboard
Jul 3, 2026
Merged

feat(treelist): full APG tree keyboard pattern + aria-level/posinset/setsize#3409
cixzhang merged 2 commits into
mainfrom
navi/feat/treelist-apg-keyboard

Conversation

@cixzhang

@cixzhang cixzhang commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Problem

TreeList renders a role="tree" with role="treeitem" rows but does not implement the WAI-ARIA APG Tree View keyboard model. Keyboard users could not navigate the tree with arrow keys, there was no roving tab stop, and treeitems were missing the structural ARIA (aria-level, aria-posinset, aria-setsize) that assistive tech needs to convey hierarchy and position. The interim fix in #3344 made parent rows keyboard-expandable via a real toggle button; this PR completes the full pattern (complex-1).

Fix

A standalone keyboard handler on the role="tree" container plus roving tabindex managed within TreeList — no dependency on any shared composite/list hook, since tree navigation (visible-order traversal + expand/collapse) is its own model:

  • Roving tabindex — exactly one treeitem row is tabbable (tabIndex=0) at a time; the rest are -1. The default tab stop is the selected item, else the first enabled row. Arrow keys move the tab stop and focus together.
  • Focusable element — the treeitem row (the <li>) is the roving-tabindex focus owner. Inner actions (link/button/chevron) are removed from the tab order (tabIndex=-1); Enter/Space on the row forwards activation to the inner action, preserving existing click/href/onClick behavior. The :focus-visible outline is bound to each row's own treeitem via a scoped StyleX marker so focusing a parent doesn't leak the ring onto descendants.
  • Keyboard model (over visible treeitems in DOM order; collapsed subtrees aren't rendered, so they're naturally excluded):
    • ArrowDown/ArrowUp — next/previous visible row, skipping disabled.
    • ArrowRight — collapsed parent → expand; expanded parent → first child; leaf → no-op.
    • ArrowLeft — expanded parent → collapse; else → move to parent row.
    • Home/End — first/last visible row.
    • Enter/Space — activate the row's action, or toggle expansion for a parent without its own action.
    • Typeahead — printable characters move focus to the next row whose label matches (500 ms buffer reset).
  • Structural ARIA — every treeitem now sets aria-level, aria-posinset, and aria-setsize, threaded through the existing renderItems recursion.

Order/parent/child resolution uses querySelectorAll('[role="treeitem"]') over the tree ref (visible DOM order) plus data-tree-* attributes for level/id/disabled.

Tests

Added coverage in TreeList.test.tsx: aria-level/aria-posinset/aria-setsize at multiple depths; roving tabindex (exactly one tabbable row; defaults to selected); ArrowDown/Up (incl. skipping disabled); ArrowRight expand-then-enter-child and leaf no-op; ArrowLeft collapse-then-parent; Home/End; Enter/Space activation and parent-toggle fallback; typeahead. All existing tests continue to pass. pnpm -F @astryxdesign/core typecheck clean, eslint clean, vitest 45/45 pass.

Part of the accessibility & keyboard-management program (#3343). Completes complex-1 (the full APG tree pattern on top of the interim #3344 toggle fix).

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 3, 2026 2:04am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.6KB 0B

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

github-actions Bot added a commit that referenced this pull request Jul 2, 2026
@cixzhang

cixzhang commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Hmm are we able to use useListFocus? If not, can we break out a useTreeFocus?

@cixzhang cixzhang force-pushed the navi/feat/treelist-apg-keyboard branch from d58e46f to f571efb Compare July 2, 2026 19:31
@cixzhang cixzhang marked this pull request as ready for review July 2, 2026 19:34
@cixzhang cixzhang enabled auto-merge (squash) July 2, 2026 19:34
github-actions Bot added a commit that referenced this pull request Jul 2, 2026
@cixzhang cixzhang force-pushed the navi/feat/treelist-apg-keyboard branch from f571efb to 7328f79 Compare July 2, 2026 20:53
@cixzhang

cixzhang commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Good question. useListFocus doesn't fit directly — a tree's ArrowLeft/ArrowRight do expand/collapse and move to parent/child (APG tree semantics), not the linear prev/next that useListFocus provides. ArrowUp/Down/Home/End are linear roving like useListFocus, but the horizontal axis is fundamentally different.

So going with your second suggestion: extracting a dedicated useTreeFocus hook (a peer of useListFocus/useGridFocus) that owns the full APG tree keyboard pattern. Pushing that shortly.

@cixzhang cixzhang force-pushed the navi/feat/treelist-apg-keyboard branch from ad97afb to 10e8607 Compare July 3, 2026 02:01
github-actions Bot added a commit that referenced this pull request Jul 3, 2026
@cixzhang cixzhang merged commit f57fb72 into main Jul 3, 2026
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants