td-has-header

Data cells in large tables must have an associated header

automaticWCAG 1.3.1 (A)confidence: highseverity: seriousperceivable

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 headers attribute (trusted here; whether it resolves to real <th> ids is table-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

pass
<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.

fail
<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

  1. Look at the table the report names on screen.
  2. 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.
  3. 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).

View source