empty-table-header
Table header cells must not be empty
Checks that table header cells (<th>, or any element with role="columnheader"/"rowheader") have visible text content. A header named only via aria-label/aria-labelledby is also flagged, since real screen-reader/browser support for that is inconsistent.
Applies to. Applies to <th> elements that don't carry a conflicting explicit role, plus any element (native <th> or not) with role="columnheader" or role="rowheader" (`th:not([role]), [role="columnheader"], [role="rowheader"]`): a <th> that explicitly restates role="columnheader"/"rowheader" is still covered via the second clause, but a <th role="presentation"> (no longer meaningfully a header) is correctly excluded, and an ARIA-role-only header (e.g. a <div role="columnheader"> in a role="grid"/role="table" widget) is caught too.
Expectation. The header cell has visible text content. A <th> named only via aria-label/aria-labelledby (no visible text) is ALSO flagged, not treated as equivalent: aria-label support on <th> is inconsistent in practice. NVDA+Firefox and iOS VoiceOver+Safari ignore it entirely (only visible text is announced), JAWS+Chrome/IE11 also only announce visible text in the header cell itself. Visible text is the one mechanism confirmed to work across every tested combination. See https://html5accessibility.com/stuff/2024/05/22/not-so-short-note-on-aria-label-usage-big-table-edition/.
Examples
<table><tr><th></th></tr></table>
The header cell has no text content and no accessible name.
<table><tr><th>Name</th></tr></table>
The header cell has visible text content.