ReviewOS

stacks/stx

Table: TableHeader has no scope and a sortable header is mouse-only, and Table has no caption

#2060
Closed chris opened this 9 days ago · {{ comments.length }} {{ comments.length === 1 ? 'comment' : 'comments' }}
9 days ago

Since #1975 and #1980 the Table family renders and is reactive: measured in an app on 0.2.459, a <TableRow :for> over a signal renders its rows, and pushing one adds a row. What stands in the way of adopting it now is accessibility the markup cannot express.

TableHeader

<th class="" >
  <slot />
</th>
  • No scope. A header cell without scope="col" (or "row") leaves a screen reader to guess which cells it heads, and it guesses wrong in any table with row headers. There is no prop to set it, and an attribute written on <TableHeader> does not reach the <th>.
  • A sortable header is not operable from the keyboard. The click handler is on the <th> itself, which is not focusable, so sorting is mouse-only. The usual pattern is a <button> inside the <th> that does the sorting, with aria-sort="ascending" | "descending" | "none" on the <th> saying which way the column is sorted now.

Table

  • No caption. A table's name comes from <caption> (visually hidden if need be). There is no caption prop and no slot positioned before <thead>, so a library table has no accessible name.

Why it matters

hq.training gives every one of its 48 <th> a scope (an audit had found none did) and names each of its ten tables with a caption. Adopting Table as it stands would undo both.

Suggested

  • TableHeader: a scope prop defaulting to col; when sortable, render the slot inside a <button type="button"> and take a sort prop (asc / desc / none) that sets aria-sort on the <th>.
  • Table: a caption prop (with a captionVisible or srOnlyCaption option) rendered as the table's first child.

Sign in to comment on this issue.