← Back to Accessalyze

By Genesis AI Services · April 21, 2026 · 7 min read · Tables

How to Make HTML Tables Accessible

Core rule: Data tables must use <th> with scope attributes, a <caption>, and <thead>/<tbody>. Layout tables must have role="presentation" and no table header elements.

Screen readers navigate tables differently than sighted users. They read cell by cell and need explicit header associations to announce "row: Product name, column: Price — $29" rather than just "$29" with no context. Without proper markup, data tables are meaningless to blind users.

The Two Types of Tables

1. Data Tables

Data tables present structured information — pricing, schedules, comparisons, statistics. They need full accessibility markup.

See how 321 websites scored →

View the 2026 Report

2. Layout Tables

Layout tables (using <table> for visual positioning) are a legacy pattern that should be avoided. If you must use one, remove all semantic meaning:

<table role="presentation">
  <tr>
    <td>Left column content</td>
    <td>Right column content</td>
  </tr>
</table>

Simple Data Table: Required Markup

<!-- BAD: no headers, no caption -->
<table>
  <tr><td>Plan</td><td>Price</td><td>Users</td></tr>
  <tr><td>Starter</td><td>$9</td><td>1</td></tr>
  <tr><td>Pro</td><td>$29</td><td>5</td></tr>
</table>

<!-- GOOD: headers, scope, caption, thead/tbody -->
<table>
  <caption>Pricing plans comparison</caption>
  <thead>
    <tr>
      <th scope="col">Plan</th>
      <th scope="col">Monthly price</th>
      <th scope="col">Users included</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Starter</th>
      <td>$9</td>
      <td>1</td>
    </tr>
    <tr>
      <th scope="row">Pro</th>
      <td>$29</td>
      <td>5</td>
    </tr>
  </tbody>
</table>

The scope Attribute

The scope attribute tells screen readers which cells a header applies to:

The caption Element

The <caption> element is the accessible name for the table. Screen readers announce it when the user navigates to the table. It must be the first child of <table>.

<table>
  <caption>Q1 2026 sales figures by region</caption>
  ...
</table>

If you don't want the caption to be visible, you can visually hide it while keeping it accessible:

caption.sr-only {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0,0,0,0);
}

Complex Tables: Multiple Header Levels

For tables with row and column spans (irregular headers), use id and headers attributes instead of scope:

<table>
  <caption>Sales by product and quarter</caption>
  <thead>
    <tr>
      <th id="product">Product</th>
      <th id="q1" colspan="2">Q1</th>
      <th id="q2" colspan="2">Q2</th>
    </tr>
    <tr>
      <td></td>
      <th id="q1-units" headers="q1">Units</th>
      <th id="q1-rev" headers="q1">Revenue</th>
      <th id="q2-units" headers="q2">Units</th>
      <th id="q2-rev" headers="q2">Revenue</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th id="widget-a" headers="product">Widget A</th>
      <td headers="q1 q1-units widget-a">320</td>
      <td headers="q1 q1-rev widget-a">$9,600</td>
      <td headers="q2 q2-units widget-a">410</td>
      <td headers="q2 q2-rev widget-a">$12,300</td>
    </tr>
  </tbody>
</table>

Responsive Tables

Responsive tables that hide columns on mobile must maintain header associations even when columns are hidden or reordered via CSS. Never use CSS to visually reorder table cells in a way that breaks the DOM order, as screen readers read DOM order.

Common Table Accessibility Failures

Find Table Accessibility Issues on Your Site

Accessalyze detects missing table headers, scope attributes, and other WCAG 1.3.1 violations automatically.

Scan for Table Issues →

← Back to Accessalyze

Accessalyze - Free WCAG 2.1 scanner that writes the fix code for you | Product Hunt

See real website accessibility scores: Browse 244+ free accessibility audits →

Try it yourself

Enter your website URL to get a free accessibility score.

Check your website accessibility score free Scan Now →