fix(Pagination): announce page size selected state and active pager (ENG-209890, ENG-209864) - #1167
Open
ksharma-c-eightfold wants to merge 3 commits into
Conversation
…9890) The page size dropdown rendered a Menu (role=menu > role=menuitem) inside the Dropdown's role=listbox overlay, so screen readers announced only the option name and position with no selected state (WCAG 4.1.2 Name, Role, Value). Mark the Menu role=presentation, give each item role=option plus aria-selected, and declare aria-haspopup=listbox on the trigger. Arrow-key focus movement, keyboard behaviour and styling are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1167 +/- ##
==========================================
- Coverage 85.41% 85.41% -0.01%
==========================================
Files 1231 1231
Lines 21719 21719
Branches 8263 8263
==========================================
- Hits 18552 18551 -1
- Misses 3079 3080 +1
Partials 88 88 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…209864) Pager buttons passed a boolean to aria-current, which React serializes to aria-current="true" on the active page and aria-current="false" on every other one. NVDA announces the generic "true" token as the bare word "current", giving no indication of what is current. Emit the "page" token when active and omit the attribute otherwise, at all three call sites (first page, mapped middle pages, last page). This matches the ARIA spec, the APG pagination pattern, and Breadcrumb.tsx, which already uses 'aria-current': ariaCurrent ? 'page' : null. Accessible names, the active class, and pagination behaviour are unchanged. Adds a test pinning the attribute value so a boolean cannot regress it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Pagination page-size dropdown rendered a
Menu(role=menu>role=menuitem) inside the Dropdown'srole=listboxoverlay. Screen readers announced only the option name and position — "20 / page, 1 of 3" — with no selected state.menuitemis not a valid child oflistboxand carries no selected state in ARIA.WCAG 4.1.2 Name, Role, Value.
Separately, the pager buttons passed a boolean to
aria-current. React serializesaria-*booleans to strings, so the active page renderedaria-current="true"and every inactive one renderedaria-current="false". NVDA maps the generictruetoken to the bare word "current", so activating a page announced only "current" with no indication of what is current.WCAG 1.3.1 Info and Relationships.
Fix
Page-size dropdown (ENG-209890):
role="option"andaria-selected={item === _pageSize}Menugetsrole="presentation"so theulno longer claimsrole="menu"inside the listboxDropdowngetsariaHaspopupValue="listbox"so the trigger declares its popup typeResulting structure:
listbox > (presentation ul) > (presentation li) > option[aria-selected].Follows the existing
Select.tsxconvention for option/listbox pickers in this repo.Pager buttons (ENG-209864):
aria-currentcall sites inPager.tsx(first page, mapped middle pages, last page) now emitaria-current="page"when active and omit the attribute otherwisepageis the token ARIA and the APG pagination pattern define for this case; NVDA announces it as "current page"undefinedrather thanfalsealso drops the pointlessaria-current="false"from every inactive pager buttonMatches
Breadcrumb.tsx, which already does'aria-current': ariaCurrent ? 'page' : null.Pager.tsxwas the last component in the library emitting a booleanaria-current.No change to the pager buttons' accessible names, the
activeclass, styling, or any pagination behaviour.JIRA ID
Verification
DOM-verified in Chrome against Storybook
pagination--change-page-size:aria-haspopup=listbox,aria-expandedtoggles false -> truerole=listbox;uland alllirole=presentationrole=option;aria-selectedreadstrueon the current size,falseon the restrole=menuitem/role=menutsc --noEmitclean, Pagination + Table + PersistentBar + Carousel suites pass (37 suites / 422 tests). One snapshot line updated for the newaria-haspopup.For the pager fix, a unit test pins the attribute value at all three call sites (first page, a middle page, the last page active) and asserts exactly one button carries it — a boolean would silently regress this otherwise. Snapshots across Pagination, Table, PersistentBar and Carousel drop from 24
aria-currentoccurrences to 4, one per rendered pager.Manual NVDA confirmation that the announcement is "current page" rather than the bare "current" still needs a Windows box and has not been done here.
No CSS, no focus-order, and no keyboard-behaviour changes —
List/Dropdownkey handlers navigate via refs andactiveElement, never by role.Screen-reader wording note:
option+aria-selectedis what produces "selected" / "not selected". VoiceOver speaks only the selected state and stays silent on unselected rows; NVDA announces both.Not addressed
Menu.types.tsdeclaresmenuItemRole/menuButtonRole/menuButtonHasRolethatMenu.tsxnever destructures🤖 Generated with Claude Code