Skip to content

feat: structural accessibility for data table - #5082

Open
oleksandrzavarzin-callstack wants to merge 5 commits into
callstack:mainfrom
oleksandrzavarzin-callstack:feat/structural-accessibility-for-DataTable
Open

feat: structural accessibility for data table#5082
oleksandrzavarzin-callstack wants to merge 5 commits into
callstack:mainfrom
oleksandrzavarzin-callstack:feat/structural-accessibility-for-DataTable

Conversation

@oleksandrzavarzin-callstack

@oleksandrzavarzin-callstack oleksandrzavarzin-callstack commented Aug 28, 2026

Copy link
Copy Markdown

Motivation

DataTable's structure was purely visual: bare Views and TouchableRipples with no roles, indices or header relationships, so a screen reader conveyed no table context at all. A user heard "159" with no way to know it was the Calories column of row 3.

Changes

  • Web semantics: role="table" with aria-rowcount/aria-colcount, role="row" with aria-rowindex, role="columnheader" with aria-sort, role="cell" with aria-colindex.
  • Native fallback: table roles map to no accessibility trait on iOS or Android, so a row is announced as one item: "Dessert, Frozen yogurt, Calories, 159, row 3 of 6". It falls back to per-cell focus when a row holds interactive or non-text content, so nothing becomes unreachable. nativeFocusMode overrides the choice.
  • Non-actionable rendering: rows, cells and titles with no touch handler render a plain View.
  • Sort state: aria-sort on web, folded into the accessible name on native, with an announcement on change since focus stays on the header. New sortAccessibilityLabels localizes the wording.
  • Reanimated: the sort indicator moves off legacy Animated, matching Switch and Checkbox. Reduced motion is suppressed via ReduceMotion.Always instead of a manual snap, and 150ms becomes theme.motion.duration.short3 (same value).
  • Pagination: real control names, localizable through one labels prop. Data-dependent values are functions, so pluralization stays with the app's own i18n.
  • Columns: optional columns describes width and alignment once instead of repeating style on a title and every cell. New align composes with numeric, which is unchanged and not deprecated.
  • Text scaling: numberOfLines is honoured exactly at every font scale; the default is one line at the default scale and unclamped above it.
  • Tokens: separators move from surfaceVariant to outlineVariant, matching Divider.

Button gains aria-expanded, needed by the rows-per-page anchor which never reported that it opens a menu.

Breaking changes

Rows, cells and titles without a touch handler render a plain View. They lose the ripple, the web hover background and keyboard focus, and no longer report a disabled state.

// Before (v5): announced as a disabled control
<DataTable.Row>
  <DataTable.Cell>{item.name}</DataTable.Cell>
</DataTable.Row>

// After (v6): pass a handler if the row is meant to be pressable
<DataTable.Row onPress={() => select(item)}>
  <DataTable.Cell>{item.name}</DataTable.Cell>
</DataTable.Row>

Other important changes:

  • Rows are announced as a single item on native, and need rowCount and firstRowIndex for correct positions when paginating.
  • Pagination default labels changed, so tests querying the old placeholders need updating.
  • numeric now applies tabular figures, the one visual change to existing tables.

Related issue

Child of the "Modernize non-standard components" effort; no issue exists yet.

Test plan

  • yarn typescript, yarn lint clean
  • yarn test 806 passed, 167 snapshots. The 2828-line snapshot is replaced with 8 small per-component snapshots plus named contract assertions, since the old blob is where the aria-disabled regression sat unnoticed.
  • Web: verified against the live accessibility tree. Correct roles, counts and indices, aria-sort cycling, and no aria-disabled on any row, cell or title.
  • Android: TalkBack on an emulator, cross-checked with adb shell uiautomator dump. Tapping a row focuses that row, each header is its own stop, and a row containing an interactive element (e.g. Checkbox) falls back to per-cell focus.
  • iOS: Accessibility Inspector against the Simulator.
  • Reduced motion, 200% font scale, and RTL mirroring.

Screenshots:

Screenshot 2026-08-28 at 16 16 50 Screenshot 2026-08-28 at 16 13 27 Screenshot 2026-08-28 at 16 17 54

@oleksandrzavarzin-callstack
oleksandrzavarzin-callstack marked this pull request as ready for review August 28, 2026 13:52
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.

1 participant