ADA Compliance Professionals

    Table headers must have visible text

    Last updated:

    Who it helps:
    Blind
    Standard:
    WCAG 2.2 Level A

    Table headers must not be empty and require visible text

    Every table header cell (th) needs visible, descriptive text.

    This applies to column and row headers in data tables on web and mobile web.

    It affects screen reader users, low-vision users, and anyone scanning data for meaning.

    Why It Matters

    Without visible header text, people cannot tell what a column or row represents. This creates confusion for sighted users and those with low vision or zoom who rely on visible labels.

    Screen reader users depend on header associations to understand cell context. If a th is empty or labeled only via ARIA, the header may be skipped in some reading modes, causing data to lose meaning.

    Cognitive load increases when users must guess the purpose of columns or rely on memorizing positions instead of reading clear labels.

    Common Causes

    • Empty <th> elements left as placeholders.
    • Using only icons, SVGs, or background images in headers with no accompanying text.
    • Adding aria-label or title to a header instead of visible text.
    • CSS that hides header text (display:none, visibility:hidden, clip, or off-screen techniques).
    • Using <td> for headers instead of <th>, breaking announced relationships.
    • Responsive layouts that drop or replace header text with icons at narrow widths.

    How to Fix

    1. Put clear, visible text inside every <th>.
      • Example labels: Name, Status, Due Date, Amount.
      • Recommendation: keep labels short (1–3 words) and unambiguous.
    2. Use proper header semantics.
      • Simple tables: use <th scope="col"> for column headers and <th scope="row"> for row headers.
      • Complex tables: use headers/id to explicitly associate data cells with multiple headers.
    3. Do not rely on ARIA-only names.
      • aria-label or aria-labelledby does not replace visible header text. Use ARIA only as a supplement if needed.
    4. Ensure visibility across breakpoints.
      • At narrow viewports and at 400% zoom, the header text must remain visible and not be replaced by icon-only UI.
      • If space is tight, wrap text or use acceptable abbreviations. Provide a longer label in the table caption or nearby text if needed.
    5. Icon support (optional).
      • If including an icon (e.g., sort indicator), keep the text visible. Mark decorative icons aria-hidden="true".

    How to Test

    Visual check

    • Inspect each <th>. Is there readable text visible on screen at normal and narrow widths? At 400% zoom?

    Programmatic check

    • Verify table headers use <th>, not <td>.
    • Confirm <th> elements are not empty and not hidden via CSS.
    • For simple tables, ensure appropriate scope attributes. For complex tables, verify headers/id mappings.
    • Optional quick scan in the console:
    CSS
    const emptyHeaders = [...document.querySelectorAll('th')]
      .filter(th => !th.textContent.trim());
    console.log(emptyHeaders);
    
    Screen reader check (NVDAJAWSVoiceOver)
    - Navigate the table by cell. Move to a data cell and confirm the associated header text is announced.
    - Move across columns and rows and verify the correct header is read each time.
    
    Keyboard check (when headers contain controls)
    - If headers include sortfilter buttons, ensure they receive focus, have visible text or an accessible name, and do not obscure the header label.

    Mobile/touch check

    • In a narrow viewport, confirm header text remains visible and readable. No icon-only headers.

    Good Example

    HTML
    <table>
      <caption>Quarterly Revenue Report</caption>
      <thead>
        <tr>
          <th scope="col">Quarter</th>
          <th scope="col">Revenue</th>
          <th scope="col">Change</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">Q1</th>
          <td>$250,000</td>
          <td>+4%</td>
        </tr>
        <tr>
          <th scope="row">Q2</th>
          <td>$265,000</td>
          <td>+6%</td>
        </tr>
      </tbody>
    </table>

    Bad Example

    HTML
    <table>
      <thead>
        <tr>
          <th></th> <!-- empty header -->
          <th aria-label="Revenue"></th> <!-- ARIA-only, not visible -->
          <th><span class="sr-only">Change</span><svg aria-hidden="true"></svg></th> <!-- no visible text -->
        </tr>
      </thead>
    </table>

    Quick Checklist

    • Each <th> contains short, visible, descriptive text.
    • No empty headers and no icon-only headers.
    • Do not rely on aria-label/title as the sole header name.
    • Use <th> (not <td>) for headers and apply scope or headers/id correctly.
    • Header text stays visible at narrow viewports and 400% zoom.
    • Decorative icons in headers are aria-hidden and do not replace text.
    • Screen readers announce correct headers when navigating data cells.