td-has-header
Data cells in large tables must have an associated header
Checks that every <td> in a large, simple (no colspan/rowspan) table has an associated header, via a headers attribute, an implicit column <th> above it, or an implicit row <th> to its left.
Applies to. <table> elements with at least 4 rows and at least 4 columns (a "large" table, where implicit row/column header association is useful; small tables are usually self-evident), and with NO colspan/rowspan anywhere in the table (a rowspan of 0, which grows the cell to the end of its row group, is a span; a colspan of 0 is 1). A table whose role (the role attribute's first known, non-abstract token, in any case) is anything but table, grid or treegrid, such as a layout table with role="presentation", is left out: it has no data cells.
Expectation. A table with no header cell at all is asked about once, on the table: markup can't tell a layout table, which needs no headers, from a data table without them. In a table with header cells, every non-empty <td> has an associated header, via one of:
- a non-empty
headersattribute (trusted here; whether it resolves to real<th>ids istable-headers-attr-valid's concern, not this rule's), OR - an implicit column header: a header cell in the same column, in an earlier row, OR
- an implicit row header: a header cell earlier in the same row.
A header cell is a <th> with no other role, or any cell with role="columnheader" or role="rowheader" (such a <td> is a header, not a data cell). A <td> with no text and no content that could carry a name (an image, a control, an element with an ARIA label) holds no data, so it needs no header; the empty corner cell above row headers is the usual case.
What a finding reports.
row,column: where the cell sits in the table, counting from 0: rows from the top, cells from the left within the row.
These data.details fields help to read and reproduce a finding. Apart from reasonCode and the few fields Output schema names as stable, they are not a stable contract: a field may be renamed or dropped in a minor release, so don’t build on them.
Examples
<table><tr><th></th><th>Q1</th><th>Q2</th><th>Q3</th></tr><tr><th>North</th><td>1</td><td>2</td><td>3</td></tr><tr><th>South</th><td>4</td><td>5</td><td>6</td></tr><tr><th>East</th><td>7</td><td>8</td><td>9</td></tr></table>
Every <td> has both an implicit row header (<th> earlier in its row) and column header (<th> earlier in its column). The 4x4 minimum size isn't optional — this rule is inapplicable to smaller tables.
<table><tr><th>Name</th><th>Team</th><th>Room</th><td></td></tr><tr><td>Ana</td><td>Sales</td><td>2.01</td><td>Mon</td></tr><tr><td>Bo</td><td>Legal</td><td>2.04</td><td>Tue</td></tr><tr><td>Chen</td><td>IT</td><td>3.10</td><td>Fri</td></tr></table>
The header row stops one column short: the last column's cells have no row, column, or headers-attribute association. A table with no header cell at all is asked about instead, since it may be a layout table, which needs none.
How to check
When this rule can’t decide, it reports cantTell and leaves the question to a person. Each question it can ask is listed here under its reason code, with the steps to answer it. A finding carries the same steps as howToCheck, in the scan’s language. See Output schema.
TD_TABLE_NO_HEADERS
- Look at the table the report names on screen.
- Decide whether it presents data in rows and columns, with a top row or first column that labels the rest, or only lays out content side by side.
- If it holds data, inspect the label row or column and check whether it uses <th> elements (or headers attributes).
Pass. The table only lays out content (and should say so with role="presentation"), or its labelling cells are marked as headers.
Fail. The table holds data but no cell is marked as a header, so screen reader users are not told what each value means, which fails WCAG 1.3.1 (F46).