Layout Table With Data-Table Markup

ID

html.layout_table_semantic_markup

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Html

Tags

accessibility, reliability

Description

Flags a table that declares itself layout and then carries the markup only a data table has.

Rationale

role="presentation" (and its synonym role="none") takes the table out of the accessibility tree: a screen reader reads the cells as a plain run of content and never announces a row or column position. That is the right declaration for a table used to place things on a page. Data-table markup on such a table is a contradiction the author has not noticed - either the role is wrong and the table really presents data, in which case the role is hiding its structure from every assistive technology, or the markup is left over from a data table it used to be and is now dead weight that the next reader will trust.

<table role="presentation">                  <!-- FLAW - a header cell in a layout table -->
  <tr><th>Menu</th><td>Content</td></tr>
</table>

<table role="none" summary="Page layout">    <!-- FLAW - summary describes a data table -->
  <tr><td>Menu</td><td>Content</td></tr>
</table>

<table role="presentation">                  <!-- FLAW - so does a caption -->
  <caption>Layout</caption>
  <tr><td>Menu</td><td>Content</td></tr>
</table>

<table role="presentation">                  <!-- FLAW - scope belongs to a header cell -->
  <tr><td scope="row">Menu</td><td>Content</td></tr>
</table>

<table role="presentation" summary="">       <!-- OK - the legacy way to say "layout" -->
  <tr><td>Menu</td><td>Content</td></tr>
</table>

<table role="presentation">                  <!-- OK - cells and nothing else -->
  <tr><td>Menu</td><td>Content</td></tr>
</table>

<table>                                      <!-- OK - a data table, where it belongs -->
  <caption>The team</caption>
  <tr><th scope="col">Name</th><th scope="col">Role</th></tr>
  <tr><td>Ada</td><td>Owner</td></tr>
</table>

The criteria are deliberately narrow on both sides.

The table has to say it is layout - role="presentation" or role="none". The rule never infers that from a missing header row, because a missing header row is the very thing html.table_headers asks for: inferring it would make one piece of markup a finding of both rules, with opposite advice. A table that declares no role and does not read as a grid of data is judged by neither.

The markup asked about is the four things that exist only to describe a grid of data: a <th> header cell, a <caption>, a summary describing the table in prose, and scope on a cell. An empty summary is the opposite signal - it is how HTML 4 pages declared a table to be layout - so it is not a finding. Only the table’s own markup counts: a data table nested inside a layout wrapper keeps its own caption and headers and is judged as the data table it is.

Remediation

Decide which kind of table it is. If it lays out the page, drop the data-table markup - and better still, replace the table with CSS grid or flex layout, which needs no role to be understood:

<table role="presentation">
  <tr><td>Menu</td><td>Content</td></tr>
</table>

If it presents data, drop the role and finish the job: a caption, a header row marked with <th>, and a scope on each header.

<table>
  <caption>The team</caption>
  <tr><th scope="col">Name</th><th scope="col">Role</th></tr>
  <tr><td>Ada</td><td>Owner</td></tr>
</table>

© 2026 Xygeni. All rights reserved