Row Header Column for ui5-table (ARIA role="rowheader")
Contextβ
Why: BLI β let a consumer mark one column as the row's header (ARIA role="rowheader") so assistive technologies use that cell as the row's identifier, and the Table shortens the (otherwise long) row-focus announcement to just that identifier. Lightweight accessibility enhancement ahead of the richer ID-column work (FIORITECHP1-36169); must not block it.
Current behavior improved: On row focus, TableCustomAnnouncement._handleTableRowFocusin enumerated every cell as header, cell, header, cell, β¦, producing long announcements. No rowheader role existed before β cells were only gridcell / columnheader.
Designβ
- No new property. The consumer sets the native
role="rowheader"attribute directly onui5-table-celland owns adding/removing it. Theroleattribute is the single source of truth. - The row template's
cell.role ??= cell.ariaRoleis intentional (a directly-setrolewins over the default) and stays untouched. - "At most one row header per row" is documented, not enforced (matches HTML).
- Popin priority is handled by documentation only β no Table popin code. Under
overflow-mode="Popin", set a highimportanceon the correspondingui5-table-header-cellso the row-header column stays visible / pops in last. (Popin ordering already protects the highest-importancecolumn; a mis-popped row header is an application concern per the BLI.)
Implementationβ
TableCellBase.tsβariaRoleconverted from a field to a getter returning"gridcell"(so subclasses can override as accessors); added_roleBeforePopin: string | nullstash field.TableHeaderCell.tsβariaRolefieldβgetter returning"columnheader".TableCell.tsβariaRolegetter:return this.role ?? this._roleBeforePopin ?? "gridcell";β preserves any consumer-set role and restores it after popin clears the attribute. Class JSDoc gains an### Accessibilitysection documentingrole="rowheader", the one-per-row note, and the Popinimportanceguidance.TableRowTemplate.tsxβ one added line in the popin branch:cell._roleBeforePopin = cell.role;beforecell.role = null. Settingcell.role = nulldeletes theroleattribute, so without the stash the consumer'srole="rowheader"would be lost on a popin round-trip. The restore (??= cell.ariaRole) and colindex guard lines are original/untouched.TableCustomAnnouncement.tsβ_handleTableRowFocusin:const rowHeaderCell = row._visibleCells.find(c => c.ariaRole === "rowheader"); if found, announce only that cell (lessDetails), else the original[header, cell]*enumeration. Row-level state (index/selected/navigable/actions/navigated) is unchanged.Table_Acc.htmlβ a "Row Header"Off/Onselect setsrole="rowheader"on the product-name cells and raises#productColimportanceto a high value when on (restores0when off), demonstrating the Popin guidance.
Testsβ
Table.cy.tsxβ "keeps the consumer role=rowheader and its aria-colindex"; "restores role=rowheader after a popin round-trip".TableCustomAnnouncement.cy.tsxβ "should announce only the row header cell when defined".
Not in scopeβ
- Any Table popin code for row headers (documentation handles it).
- A
rowHeaderproperty on either cell type. - Enforcing a single row-header column; cell-level
accessibleName/accessibleNameRef. - Scoped non-label exclusion from the announcement:
aria-hidden/data-ui5-acc-textwas rejected because it also hides content when the cell itself is focused directly, which is not the intended behavior.
Verificationβ
- From
packages/main:yarn test:cypress:single cypress/specs/Table.cy.tsxandcypress/specs/TableCustomAnnouncement.cy.tsxβ all green (specs consume.tsdirectly). - Manual: open
test/pages/Table_Acc.html, set Row Header = On, shrink the table so columns pop in, confirm the product column stays. Cross-check the row-focus announcement withchrome-devtools-mcp:a11y-debugging/ ACC experts against snippix 154114.