Clear aria-activedescendant while the highlighted row is unrendered; release 0.7.3 - #22
Conversation
…release 0.7.3 Virtualization drops off-window rows from the DOM, but updateActiveDescendant() set aria-activedescendant from the highlight index unconditionally. Scrolling a virtualized list past the highlighted option therefore left the attribute pointing at an id with no element behind it, which assistive technology reads as no active option at all rather than as a stale one. Virtualization turns on automatically above ~100 rows, so this affected any large list. The attribute is now set only while the highlighted row is actually rendered, and keyboard navigation scrolls the row back into view and restores it. Verified in Chromium with real wheel scrolling: the reference exists after ArrowDown, is cleared once the row scrolls out of the window, and is restored pointing at a live element on the next ArrowDown. Also audited without finding further defects: selection guards against programmatic vs interactive clearing, tree cascade under maxSelections, native multi-select and form-reset syncing, pagination page overlap, portal teardown, accent-strict search, i18n interpolation, emitter re-entrancy, and variable-height virtualization driven through a real browser.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
forge-select | a4f582f | Commit Preview URL Branch Preview URL |
Aug 02 2026, 11:30 AM |
|
Warning Review limit reached
Next review available in: 52 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. 📝 WalkthroughWalkthroughThe change fixes ChangesVirtualized accessibility release
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ight index The first cut rebuilt the id from the highlight index and then probed the document for it, which pushed the gzipped bundle to 13,504 against the 13,500 budget. main was already sitting at 13,495, so the gate had four bytes of room and any fix of this shape would have tripped it. Reading the id off the element the render pass just marked with --highlighted removes both the id template and the lookup: the class and the id are written together in renderRow, so the element is authoritative about whether the highlighted row is currently rendered. 13,490 bytes — five below main while fixing the bug. Re-verified in Chromium across virtual (ArrowDown / scrolled away / restored / End at row 999), a non-virtualized list, and the create row: the attribute always matches the marked element's id or is absent, with no console errors.
|



What & why
An accessibility defect found while auditing the virtual-scroll and keyboard paths.
updateActiveDescendant()setaria-activedescendantfrom the highlight index unconditionally. Virtualization drops off-window rows from the DOM, so scrolling a virtualized list past the highlighted option left the attribute pointing at an id with no element behind it. WAI-ARIA requires the reference to resolve to a real element; a dangling one reads to assistive technology as no active option at all, not as a stale one. Virtualization turns on automatically above ~100 rows, so this affected any large list — the library's headline feature.Measured before the fix (jsdom, 1,000 options): highlight row 0, scroll the list, then
aria-activedescendant"forge-select-1-nav-0"document.getElementById(...)null"Option 550"The fix
The attribute is set only while the highlighted row is actually rendered, and keyboard navigation scrolls the row back into view and restores it. No API change — patch release (0.7.2 → 0.7.3).
Test plan
Updated relevant docs — n/a, no API surface change
Added a
CHANGELOG.mdentry (under the new[0.7.3]section being released here)Added/updated tests — 160 passing (was 159). New test covers all three states (set → cleared on scroll → restored on ArrowDown) and was verified to fail without the fix:
expected 'forge-select-1-nav-0' to be nullnpm run verifypasses locally — lint, format check, typecheck, coverage-enforced tests, build. Coverage up: branch 82.46 → 83.53%, lines 94.20 → 94.91%Verified in real Chromium with actual wheel scrolling, not a
scrollTopassignment:aria-activedescendantArrowDownforge-select-1-nav-0nullArrowDownagainforge-select-1-nav-1Zero console errors.
Audited without finding further defects
Recorded so the next pass can skip them: programmatic vs interactive clearing against the selection guards, tree cascade under
maxSelections,selectAllvs the cap,setValuede-duplication and the missing-value policy, native multi-select and form-reset syncing, pagination page overlap, portal teardown, accent-strict search,classNamewhitespace handling, i18n interpolation reaching the DOM, emitter re-entrancy, and variable-height (itemHeight: "auto") virtualization driven through a real browser —scrollHeightconverges as rows are measured (21,696 → 22,224 over 600 uneven rows) and both ends of the list are reachable.Generated by Claude Code
Summary by CodeRabbit
Bug Fixes
aria-activedescendantwhen the highlighted option is no longer rendered.Chores