Table Headers Must Have Data Cells: How to Fix It

No Comments
Table headers must have data cells: how to fix it
TL;DR

A <th> that describes no data cells is a header pointing at nothing — usually a leftover from a layout table or a half-built data table. Assistive tech can’t map it to any content, so either give the header real cells to describe or drop the table markup and use CSS for layout.

What this check flags

This is the axe-core th-has-data-cells rule. It inspects every <th> and every element with role="columnheader" or role="rowheader" and confirms each one actually describes data cells. When a header sits in a table that has no data cells for it to point at, the check fails. It maps to WCAG 2.1 SC 1.3.1 Info and Relationships (Level A): a header announces what a column or row contains, and when there is nothing under or beside it, the header makes a promise the table never keeps — which is exactly the broken relationship the rule catches.

Data tables vs. layout tables

A data table presents information where a cell’s meaning depends on its row and column — a pricing grid, a schedule, a spec sheet. Those need headers so screen readers can announce context (“Price, $49”). A layout table was the old way to position page elements before CSS did the job properly. This rule fires most often when someone built a layout table but sprinkled in <th> tags — often just to bold or centre some text — with no genuine data relationship behind them.

A real failing example

Here a table has header cells but the body rows use <td> in a way that leaves one header describing nothing, plus a stray header row with no matching data:

<table>
  <tr>
    <th>Plan</th>
    <th>Price</th>
    <th>Seats</th>
  </tr>
  <tr>
    <th>Notes</th>      <!-- header with no data cells below it -->
  </tr>
</table>

The “Notes” header sits alone with no row of <td> beneath it, so th-has-data-cells flags it. The fix is to make the table an honest grid where every header owns a column of data cells:

<table>
  <thead>
    <tr>
      <th scope="col">Plan</th>
      <th scope="col">Price</th>
      <th scope="col">Seats</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Starter</td>
      <td>$49</td>
      <td>3</td>
    </tr>
  </tbody>
</table>

Now every header has a column of data underneath it, scope="col" makes the association explicit, and a screen reader can announce “Price, $49” as it moves across the row.

What a screen reader hears

SituationAnnounced to the userResult
Header with matching data cells“Price, column 2, $49”Context is clear
Header pointing at no data“Notes” — then silenceOrphaned, confusing
Layout table with stray <th>Reads it as a data header falselyTable mode fires for no reason
Proper layout via CSS gridReads content in order, no table modeClean and predictable

How to detect it

  1. Screaming Frog — run a crawl with a custom extraction (or the built-in accessibility integration if enabled) to pull <table> markup, then flag tables where a <th> count outstrips the <td> rows. Odd ratios are your candidates.
  2. axe DevTools — run the extension and look for the th-has-data-cells violation. It points at the exact header element with no associated data cells.
  3. Lighthouse — the Accessibility audit includes table and header checks; a failing table structure shows up in the results with the offending node.
  4. View Source / DevTools Elements — inspect the table by hand. Count the <th> cells against the body rows. A header with no column or row of <td> behind it is the failure — and often a sign the table is really doing layout.

How to fix it

If it’s a real data table

  1. Make sure every header has a full column (or row) of <td> data cells beneath or beside it.
  2. Add scope="col" or scope="row" so the header-to-cell association is explicit, not guessed.
  3. Remove any leftover header cells that describe nothing — a “spacer” or “notes” header with no data is dead weight.

If it’s really a layout table

  1. Stop using a table for layout. Rebuild the layout with CSS — flexbox or grid handles columns and alignment far better.
  2. If ripping it out now is not realistic, at minimum change every <th> to <td> and add role="presentation" to the table so assistive tech stops treating it as data.

Common mistakes

  • Bolding text with <th>. Header cells are for structure, not styling. If you just want bold or centred text, use <td> and CSS.
  • Leaving a stray header row. A “title” row of <th> spanning the top with no colspan and no data underneath each one trips the rule. Use a <caption> for a table title instead.
  • Fixing the symptom, ignoring the cause. Patching one orphaned header when the whole table is a layout hack just moves the problem. Decide first whether it is data or layout, then fix accordingly.

FAQ

Do I need scope on every header?

For a simple grid, browsers often infer the association, but adding scope="col" and scope="row" removes the guesswork and is the safest habit. For complex tables with merged cells it becomes essential. Related: scope attribute usage.

My table looks fine visually — why does it fail?

Visual appearance and the accessibility tree are two different things. A header can look aligned over a column while the markup leaves it associated with no <td> at all. The rule reads the code, not the pixels.

Is this the same as headers needing discernible text?

No — that’s a separate check about a header being empty or unreadable. This one is about a header that has text but describes no data. See ensure table headers have discernible text for the other side.

What about headers that reference IDs?

Complex tables can tie cells to headers with headers and id attributes. If those references break, you get a related failure. See table headers must reference valid IDs.

Does fixing this help SEO?

Clean, genuine data tables are easier for crawlers to parse and are the kind of structured content that can earn richer treatment in results. It also feeds the broader overlap between accessibility and SEO.

Need a full technical audit?

SEO ProCheck runs deep crawls that catch issues like this across your whole site.

Get in touch

Claude Vincent is a technical SEO consultant focused on crawlability, rendering, and AI-search visibility. He writes the field guides and case studies at SEO ProCheck, with a bias toward the durable, unglamorous work that decides whether search engines and AI answer engines can actually read and cite a site.

About SEO ProCheck

Technical SEO consulting and GEO strategy with 20 years of enterprise experience. Case studies, resources, and tools for search and AI visibility.

Work With Me

Technical SEO audits, GEO strategy, site migrations, and international SEO. Hourly consulting for teams who need hands-on support, not just reports.

Subscribe to our newsletter!

More from our blog