feat(a11y): add keyboard navigation to sidebar page tree (#1150)#1151
Conversation
Implement WAI-ARIA Treeview keyboard navigation pattern for the sidebar page tree. Arrow keys move focus between visible items, ArrowRight/Left expand/collapse parents, Enter navigates, Home/End jump to first/last. Uses roving tabindex: only the focused item has tabindex=0, all others have tabindex=-1. Focus ring uses ring-1 ring-accent. Added getVisibleItems and findParentNode utilities to page-tree.ts. Co-authored-by: Ona <no-reply@ona.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…used story The new PageTreeItem 'Keyboard focused' Storybook story was added without its corresponding visual regression baseline screenshot. The visual regression CI job fails because the snapshot file does not exist. Co-authored-by: Ona <no-reply@ona.com>
|
✅ UI verification passed — design spec compliance confirmed. Static analysis (code review against
Storybook visual regression: ✅ All story baselines match (1 test, all stories including new "Keyboard focused" story). Live site screenshots (dark, light, mobile — workspace home + editor page): ✅ Sidebar page tree renders correctly. No broken layouts, overlapping elements, or clipping. Mobile layout correct (sidebar hidden as expected). Text readable with good contrast in both themes. |
|
✅ Post-merge verification passed. E2E tests (against live site
Ad-hoc smoke tests:
Interaction smoke test (keyboard navigation feature):
Note: A transient 429 (rate limit) console error was observed during the interaction test — this is from running many sequential tests against the live site, not a regression from this PR. |
Closes #1150
What
Adds WAI-ARIA Treeview keyboard navigation to the sidebar page tree, making it fully accessible to keyboard-only and screen reader users.
How
Keyboard bindings:
ArrowDown/ArrowUp— move focus to next/previous visible treeitemArrowRight— expand collapsed parent, or move to first child if expandedArrowLeft— collapse expanded parent, or move to parent nodeEnter— navigate to the focused pageHome/End— jump to first/last visible treeitemImplementation:
tabindex="0", all otherstabindex="-1". When no item has been focused, the first visible item is tabbable.onKeyDownhandler on therole="tree"container delegates togetVisibleItems()for traversal order.onFocushandler (event delegation) syncsfocusedIdstate when items receive focus via click or programmatic.focus().ring-1 ring-accent outline-noneon the focused row.tabIndex={-1}to stay out of the tab order.New utilities in
src/lib/page-tree.ts:getVisibleItems(roots, expanded)— flat list of visible nodes in document orderfindParentNode(roots, pageId)— find a node's parent in the treeFiles changed:
src/lib/page-tree.ts— 2 new pure functionssrc/lib/page-tree.test.ts— 9 new unit tests (37 → 46)src/components/sidebar/page-tree.tsx— keyboard handler, focus state, treeRefsrc/components/sidebar/page-tree-item.tsx—focusedId/tabbableIdprops, roving tabindex, focus ring,data-page-idattributesrc/components/sidebar/page-tree-item.stories.tsx— new "Keyboard focused" storye2e/sidebar-keyboard-nav.spec.ts— 10 E2E tests.agents/conventions.md— documented roving tabindex pattern.agents/quality.md— updated test countsTesting
pnpm lint— 0 errors (52 pre-existing warnings)pnpm typecheck— cleanpnpm test— 144 files, 1971 tests passedpnpm test:e2e(sidebar-keyboard-nav) — 10 tests: 8 passed, 2 skipped (no expandable items in test data). Auth timeouts are a pre-existing flaky infrastructure issue, not related to this change.Notes