Skip to content

a11y: add aria-sort, SR announcements, Enter/Space operability to sor… - #1139

Open
Emelie-Dev wants to merge 1 commit into
Stellopay:mainfrom
Emelie-Dev:fix/issue-731-sort-aria-sr-announcements
Open

a11y: add aria-sort, SR announcements, Enter/Space operability to sor…#1139
Emelie-Dev wants to merge 1 commit into
Stellopay:mainfrom
Emelie-Dev:fix/issue-731-sort-aria-sr-announcements

Conversation

@Emelie-Dev

Copy link
Copy Markdown
Contributor

a11y: add aria-sort, SR announcements, Enter/Space operability to transactions sort control

Closes #731.

Problem

components/transactions/sort.tsx renders the active sort state visually (chevron icons and order badges) but provides no equivalent for screen readers:

  • No aria-sort attribute on the column headers driving the order
  • No live-region announcement when the sort order changes
  • No explicit keyboard operability contract for Enter / Space on sort items and the secondary-sort clear chip

Solution

Scoped changes to exactly components/transactions/sort.tsx and its test file, following existing design tokens and the Radix-UI wrapper patterns already used in the app.

aria-sort semantics

Emits a visually-hidden surrogate role=rowgroup > row > columnheader tree that maps each SortField to a semantic column header carrying the correct aria-sort value:

  • Primary sort field → ascending / descending
  • Secondary sort field → other (per WAI-ARIA APG pattern for non-primary ordered columns)
  • Inactive fields → attribute omitted (equivalent to WAI-ARIA default none)

The surrogate keeps the change scoped to this file (issue scope is exactly sort.tsx) and does not couple to transactions-table.tsx, whose column labels differ from SORT_LABELS.

Live-region announcements

A role=status / aria-live=polite / aria-atomic=true region (.sr-only) announces human-readable "Sorted by Date descending, then by Amount ascending." text only when sortConfigs actually changes — an initialMountRef guard prevents the spurious page-load announcement that would otherwise fire on every first render.

Enter / Space operability

  • Each DropdownMenuItem gains an explicit onKeyDown that calls preventDefault() (important for Space, which would otherwise scroll the page) and forwards to the shared click handler, so Shift-key semantics for secondary-sort activation work identically for keyboard and pointer users.
  • The secondary-sort chip close <button> gets the same Enter/Space preventDefault-aware handler for symmetry.
  • ArrowDown / other navigation keys are a no-op (Radix handles menu navigation internally).

Menu-item ARIA state

  • DropdownMenuContentrole="menu", aria-label="Sort columns"
  • Each DropdownMenuItemrole="menuitemcheckbox" with aria-checked set to:
    • true for the primary sort column
    • mixed for a secondary sort column
    • false for inactive columns

Trigger + chip polish

  • ChevronsUpDown and X icons are aria-hidden.
  • Trigger aria-label embeds current primary / secondary state and the hint: Press Enter or Space to open the sort menu.
  • Secondary chip container is role="group" with a field+direction label; the close button gets a per-field aria-label="Clear secondary sort by <field>" rather than a generic "Close".

Tests

components/transactions/sort.test.tsx extended from 9 original tests to 4 new suites:

  1. aria-sort on columnheaders — desc, asc, secondary="other", all-none scenarios
  2. aria-live announcements — attributes asserted, initial mount silent, primary-swap text correct, primary+secondary text correct
  3. Enter / Space operability — Enter activates sort item, Space activates with preventDefault, ArrowDown is a no-op; Enter/Space activate secondary-clear with preventDefault on Space
  4. menu-item ARIA statearia-checked=true / mixed with role=menuitemcheckbox across all fields

Radix DropdownMenu mock updated to forward role, aria-checked, onKeyDown, data-testid and sets tabIndex={0} so jsdom fires keyboard events on the menu items.

Validation

  • TypeScript: 0 diagnostics across both changed files and the full workspace.
  • Lint: 0 IDE diagnostics. Uses project-consistent tokens (bg-[#160f17], border-[#2D2D2D], focus-visible:ring-*, sr-only).
  • Maintainability: pure helpers (ariaSortForField, buildSortAnnouncement) at module top; state handling uses refs + effect, no React 19 / Next 15 anti-patterns; change is strictly scoped to the two files named in the issue.

How to review

  1. Read sort.tsx top helpers → useEffect → rowgroup surrogate → live region → dropdown → secondary chip.
  2. Read sort.test.tsx new suites 1–4 after the existing describe block.
  3. Smoke-test manually: open the transactions sort dropdown with Enter, toggle Date/Amount with Space, and confirm with a screen reader (VoiceOver / NVDA) that aria-sort changes and the new order is announced on each switch.

closes #731

…t control

- Add visually-hidden rowgroup/columnheader surrogate carrying aria-sort per WAI-ARIA APG
  * primary -> ascending / descending, secondary -> other, inactive -> omitted (none default)
- Add role=status aria-live=polite aria-atomic=true region gated by initialMountRef so
  page load does not spam 'Sorted by ...' announcement; announce only on user-driven sort changes
- DropdownMenu content role=menu aria-label='Sort columns'; items role=menuitemcheckbox
  with aria-checked=true (primary) / mixed (secondary) / false (inactive)
- Explicit Enter/Space handlers with preventDefault on each sort item and the
  secondary-sort chip close button, shared click handler preserves shiftKey
  semantics for secondary-sort activation
- Secondary sort chip gains role=group with field+direction aria-label and per-field
  close-button aria-label
- Decorative ChevronsUpDown and X icons marked aria-hidden, trigger aria-label
  embeds current state and Enter/Space open hint
- Expand sort.test.tsx to 4 new suites covering aria-sort columnheader states,
  aria-live announcements (including no-spam initial mount and primary+secondary text),
  Enter/Space operability (with preventDefault assertions) and menuitemcheckbox ARIA state

Closes Stellopay#731
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@Emelie-Dev is attempting to deploy a commit to the Jagadeesh B's projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add aria-sort attributes and screen-reader sort announcements to components/transactions/sort.tsx

1 participant