squirrelscan
GitHub

th cells with no data cells: a layout table in disguise

A header with no data in its row or column usually means the table is really a layout. squirrel builds the grid and checks each one.

What an orphaned header is

A <th> describes the cells in its row or its column. A header with no <td> anywhere in either direction describes nothing, and a screen reader announces a header relationship that leads nowhere.

Almost always this means the table is being used for layout. The <th> came from a template that expected data, and the cells around it are all headers or all empty.

What squirrel checks

One check, th-has-data-cells, at severity warning. For each table that is not role="presentation" or role="none", it builds a two-dimensional grid, expanding colspan and rowspan so every grid slot maps to the cell that occupies it. Both span attributes are read case-insensitively.

For each <th> in that grid it looks for a <td> in the same row, then in the same column.

  • Warn when neither is found. Report message: N <th> element(s) without associated data cells. Items quote the header’s first 30 characters, or read row 2, col 3 when the header has no text. Up to ten are shown with a count of the rest, and a header that spans several slots is reported once.
  • Pass with All table headers have associated data cells.
  • Info with No data tables found.

Building the grid is what makes the check work on tables with merged cells, but the test runs per grid slot. A header spanning two columns whose own row has no data cell is judged column by column, so data under one of the two columns does not clear the other.

How to fix it

For a table that is really a layout, say so:

<table role="presentation">
  ...
</table>

That removes it from this check, from a11y/table-headers and from a11y/table-duplicate-name, which all skip presentational tables. a11y/td-headers-attr looks at every table regardless. Better still, replace it with CSS grid or flexbox, which is what the layout wanted in the first place.

For a real data table, check that each header has cells under or beside it. A header row followed by nothing, left in place while the data loads, produces this finding on the served HTML.

Rule IDa11y/th-has-data-cells
CategoryAccessibility
ScopePer-page
Severitywarning
Weight4/10

Enable / disable

Disable this rule

[rules]
disable = ["a11y/th-has-data-cells"]

Disable all Accessibility rules

[rules]
disable = ["a11y/*"]

Enable only this rule

[rules]
enable = ["a11y/th-has-data-cells"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

Accessibility findings ship in every audit next to the SEO, performance and agent experience rules. See Website security scan with AI for how an agent works through a report.

References

Check your site

Run squirrel audit https://example.com and open the Accessibility section of the report. Each orphaned header is quoted by its text, or located by row and column. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close