Table headers must contain descriptive text or labels

Content available in English only.

Accessibility isn’t just about avoiding violations — it’s about ensuring that everyone can use your product with confidence. This guide explains each rule’s intent, highlights common issues, and shows how to fix them according to WCAG and the European Accessibility Act (EAA).

These guidelines do not replace the official WCAG standards. They’re concise, developer-focused notes to help you identify and fix issues effectively.

Every table header (<th>) must have descriptive text — never leave it empty.

Why this matters and how to fix it

Why this matters

Screen reader users rely on table headers to understand what each column or row represents. If a <th> is empty or unclear, users cannot make sense of the associated data. This makes tables difficult or impossible to interpret, especially in data-heavy interfaces.

How to fix this issue

Ensure every <th> cell includes visible or programmatic text describing the column or row. For layout or spacer columns, do not use <th>—use <td> or mark the cell aria-hidden="true". Use scope="col" or scope="row" on <th> elements to clearly define relationships.

Automated detection · Manual review recommended

Developer guidance

Empty headings commonly occur when tables are dynamically generated or when designers include placeholder 'blank' columns for icons, menus, or controls. Never leave <th> empty—if a column has no semantic meaning, it should not be a header. Validate table markup in data grid components and reporting dashboards.


Code examples

Incorrect Implementation

<table>
  <tr><th></th><th>Price</th></tr>
  <tr><td>Apple</td><td>$1.00</td></tr>
</table>

Correct Implementation

<table>
  <tr><th scope="col">Product</th><th scope="col">Price</th></tr>
  <tr><td>Apple</td><td>$1.00</td></tr>
</table>

Real-World Examples

Before

<table class="invoice-table">
  <thead>
    <tr>
      <th></th>
      <th>Quantity</th>
      <th>Subtotal</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>#123</td>
      <td>2</td>
      <td>$200</td>
    </tr>
  </tbody>
</table>
<!-- Screen reader announces first column as 'blank' → no meaning → confusing -->

After

<table class="invoice-table">
  <thead>
    <tr>
      <th scope="col">Order ID</th>
      <th scope="col">Quantity</th>
      <th scope="col">Subtotal</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>#123</td>
      <td>2</td>
      <td>$200</td>
    </tr>
  </tbody>
</table>
<!-- Screen reader now announces correct column→value relationships -->

CSS Example (Guidance)

/* If header was used only for spacing, replace with CSS instead of empty <th> */
.invoice-table td:first-child { padding-left: 2rem; }

Manual testing

  1. 1. Turn on a screen reader (NVDA, VoiceOver, or TalkBack).
  2. 2. Navigate cell-by-cell through the table using arrow keys.
  3. • Expected: Screen reader should announce: 'Product, Apple', 'Price, $1.00', etc.
  4. • Failure: Screen reader announces: 'blank' or only cell values without header context.
  5. 3. Inspect the table in DevTools → Accessibility tree.
  6. • Ensure each <th> has text and scope="col" or scope="row".
  7. 4. If the table is interactive (sortable, filterable, editable), repeat the navigation after the UI updates to confirm header relationships remain correct.
  8. 5. Test with magnification or zoom ≥ 200% to confirm header text remains visible and readable.
eu icon getwcag

Trusted by organizations across Europe working toward WCAG and EAA conformance