Table Accessibility

Difficulty: Intermediate

Making tables accessible means ensuring that users who rely on screen readers and other assistive technologies can understand the structure and relationships within your tabular data. A sighted user can visually scan a table and understand which header applies to which data cell, but a screen reader user navigates tables cell by cell and depends on proper markup to announce the relevant headers for each cell.

The <caption> element provides a title or description for the table. It must be the first child of the <table> element. Screen readers announce the caption before the user enters the table, giving them context about what data the table contains. Think of it as a heading for the table. You can visually hide the caption with CSS if you want screen-reader-only context, but never omit it entirely from accessible tables.

The scope attribute on <th> elements explicitly declares whether a header applies to a column (scope="col"), a row (scope="row"), a column group (scope="colgroup"), or a row group (scope="rowgroup"). While modern screen readers can often infer scope for simple tables, adding it explicitly removes ambiguity, especially in complex tables with both row and column headers.

The headers attribute on <td> elements provides an explicit association between a data cell and its header cells by referencing the id values of the relevant <th> elements. This is the most robust approach for complex tables where the automatic header-inference algorithms of assistive technologies might produce incorrect results. For simple tables, scope is sufficient; reserve the headers attribute for tables with multi-level headers or irregular spanning.

The summary attribute on the <table> element was used in older HTML versions to provide a description of the table's structure for screen reader users. It has been deprecated in HTML5. Instead, use <caption> for a visible description, or provide a paragraph of descriptive text before the table and associate it using aria-describedby. For complex data tables, a brief description of how the table is organized (such as 'rows represent departments, columns represent quarters') helps users form a mental model before navigating the cells.

Code examples

Caption and scope Attributes

<table>
  <caption>Q1 2026 Sales Report by Region</caption>
  <thead>
    <tr>
      <th scope="col">Region</th>
      <th scope="col">Revenue</th>
      <th scope="col">Growth</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">North</th>
      <td>$1.2M</td>
      <td>+8%</td>
    </tr>
    <tr>
      <th scope="row">South</th>
      <td>$900K</td>
      <td>+12%</td>
    </tr>
    <tr>
      <th scope="row">East</th>
      <td>$1.5M</td>
      <td>+5%</td>
    </tr>
  </tbody>
</table>

The <caption> gives the table a descriptive title. Column headers use scope="col" and row headers use scope="row", making it clear which headers apply to which direction. A screen reader would announce 'Region: North, Revenue: $1.2M' as the user navigates.

Using the headers Attribute for Complex Tables

<table>
  <caption>Employee Skills Matrix</caption>
  <thead>
    <tr>
      <th id="name">Name</th>
      <th id="html">HTML</th>
      <th id="css">CSS</th>
      <th id="js">JavaScript</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th id="alice" headers="name">Alice</th>
      <td headers="alice html">Expert</td>
      <td headers="alice css">Intermediate</td>
      <td headers="alice js">Beginner</td>
    </tr>
    <tr>
      <th id="bob" headers="name">Bob</th>
      <td headers="bob html">Intermediate</td>
      <td headers="bob css">Expert</td>
      <td headers="bob js">Expert</td>
    </tr>
  </tbody>
</table>

Each <td> has a headers attribute listing the id values of its associated header cells, separated by spaces. For Alice's HTML skill level, headers="alice html" tells the screen reader that this cell is at the intersection of the 'Alice' row and 'HTML' column.

Accessible Table with aria-describedby

<p id="table-desc">
  The following table compares pricing plans.
  Rows represent features and columns represent plan tiers.
</p>
<table aria-describedby="table-desc">
  <caption>Pricing Plan Comparison</caption>
  <thead>
    <tr>
      <th scope="col">Feature</th>
      <th scope="col">Free</th>
      <th scope="col">Pro</th>
      <th scope="col">Enterprise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Storage</th>
      <td>5 GB</td>
      <td>100 GB</td>
      <td>Unlimited</td>
    </tr>
    <tr>
      <th scope="row">Support</th>
      <td>Community</td>
      <td>Email</td>
      <td>24/7 Phone</td>
    </tr>
    <tr>
      <th scope="row">Users</th>
      <td>1</td>
      <td>10</td>
      <td>Unlimited</td>
    </tr>
  </tbody>
</table>

The aria-describedby attribute links the table to an external paragraph that explains its structure. This is the modern replacement for the deprecated summary attribute. Combined with <caption>, it gives screen reader users both a title and a structural description.

Key points

Concepts covered

scope, caption, summary, headers Attribute, Accessible Tables