Complex Tables (Multi-Level Headers)

Create accessible complex data tables with multi-level headers using scope, id, and headers attributes.

advanced
1.3.1 Info and Relationships (Level A)4.1.2 Name, Role, Value (Level A)2.4.6 Headings and Labels (Level AA)
❌

The Bad (Inaccessible)

<table>
  <tr>
    <td colspan="2">Q1</td>
    <td colspan="2">Q2</td>
  </tr>
  <tr>
    <td>Jan</td>
    <td>Feb</td>
    <td>Mar</td>
    <td>Apr</td>
  </tr>
</table>
βœ…

Accessibility-Ready Code

<!-- Gold Standard: Multi-level headers with id/headers -->
<table class="complex-table">
  <caption>
    Quarterly Sales Report 2024
    <span class="sr-only">
      - Sales figures broken down by region and quarter
    </span>
  </caption>

  <thead>
    <!-- First header row: Quarter span -->
    <tr>
      <th scope="col" id="region">Region</th>
      <th scope="col" id="q1" colspan="3">Q1 2024</th>
      <th scope="col" id="q2" colspan="3">Q2 2024</th>
    </tr>

    <!-- Second header row: Months -->
    <tr>
      <th scope="col" headers="region"></th>
      <th scope="col" id="jan" headers="q1">January</th>
      <th scope="col" id="feb" headers="q1">February</th>
      <th scope="col" id="mar" headers="q1">March</th>
      <th scope="col" id="apr" headers="q2">April</th>
      <th scope="col" id="may" headers="q2">May</th>
      <th scope="col" id="jun" headers="q2">June</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <th scope="row" id="north">North Region</th>

      <!-- Q1 Data -->
      <td headers="north q1 jan">$45,000</td>
      <td headers="north q1 feb">$52,000</td>
      <td headers="north q1 mar">$48,000</td>

      <!-- Q2 Data -->
      <td headers="north q2 apr">$50,000</td>
      <td headers="north q2 may">$55,000</td>
      <td headers="north q2 jun">$60,000</td>
    </tr>

    <tr>
      <th scope="row" id="south">South Region</th>

      <!-- Q1 Data -->
      <td headers="south q1 jan">$38,000</td>
      <td headers="south q1 feb">$42,000</td>
      <td headers="south q1 mar">$45,000</td>

      <!-- Q2 Data -->
      <td headers="south q2 apr">$47,000</td>
      <td headers="south q2 may">$51,000</td>
      <td headers="south q2 jun">$53,000</td>
    </tr>

    <tr>
      <th scope="row" id="east">East Region</th>

      <!-- Q1 Data -->
      <td headers="east q1 jan">$62,000</td>
      <td headers="east q1 feb">$58,000</td>
      <td headers="east q1 mar">$65,000</td>

      <!-- Q2 Data -->
      <td headers="east q2 apr">$68,000</td>
      <td headers="east q2 may">$72,000</td>
      <td headers="east q2 jun">$75,000</td>
    </tr>

    <tr>
      <th scope="row" id="west">West Region</th>

      <!-- Q1 Data -->
      <td headers="west q1 jan">$41,000</td>
      <td headers="west q1 feb">$44,000</td>
      <td headers="west q1 mar">$47,000</td>

      <!-- Q2 Data -->
      <td headers="west q2 apr">$49,000</td>
      <td headers="west q2 may">$52,000</td>
      <td headers="west q2 jun">$56,000</td>
    </tr>
  </tbody>

  <tfoot>
    <tr>
      <th scope="row">Total</th>
      <th headers="q1 jan" colspan="3">$629,000</th>
      <th headers="q2 apr" colspan="3">$732,000</th>
    </tr>
  </tfoot>
</table>

The Standard

Complex tables have multi-level headers that require careful markup. Use id, headers, and scope attributes to establish relationships between headers and data cells.

WCAG Criteria

  • 1.3.1 Info and Relationships: Multi-level header structure.
  • 4.1.2 Name, Role, Value: Proper header associations.
  • 2.4.6 Headings and Labels: Clear table hierarchy.

❌ The Bad (Inaccessible)

What’s Wrong?

  1. Missing headers attribute: Screen reader can’t map data to all headers.
  2. Using only colspan: No semantic meaning, just visual.
  3. Empty <th> cells: Confusing navigation.
  4. Inconsistent header IDs: Broken references.

βœ… The Good (Accessibility-Ready Code)

Why This Works

  1. Header Hierarchy: Level 1 (quarter) β†’ Level 2 (month).
  2. headers Attribute: Links cells to ALL relevant header IDs.
  3. Scope Attributes: Defines column and row headers.
  4. Unique IDs: Each header gets a unique identifier.

Accessibility Checklist

  • Give each unique header a unique id.
  • [ ] Use colspan on headers that span multiple columns.
  • Link data cells to all applicable headers with headers="id1 id2 id3".
  • Use scope="col" for column headers.
  • Use scope="row" for row headers.
  • Include aria-labelledby or sr-only text for context.
πŸ“ Perfect Implementation Reference
<table>
  <caption>Quarterly Sales</caption>

  <thead>
    <!-- Level 1 -->
    <tr>
      <th scope="col" id="region">Region</th>
      <th scope="col" id="q1" colspan="3">Q1</th>
    </tr>

    <!-- Level 2 -->
    <tr>
      <th scope="col" headers="region"></th>
      <th scope="col" id="jan" headers="q1">Jan</th>
      <th scope="col" id="feb" headers="q1">Feb</th>
      <th scope="col" id="mar" headers="q1">Mar</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <th scope="row" id="north">North</th>

      <!-- Headers attribute links to ALL applicable headers -->
      <td headers="north q1 jan">$45,000</td>
      <td headers="north q1 feb">$52,000</td>
      <td headers="north q1 mar">$48,000</td>
    </tr>
  </tbody>
</table>

Technical Deep Dive

Screen Reader Announcements

  • NVDA: β€œNorth Region Q1 January $45,000”
  • JAWS: β€œNorth Region Q1 January $45,000”
  • VoiceOver: β€œNorth Region, Q1, January, $45,000”

Best Practice: The headers Attribute Format

For complex tables with multi-level headers, the headers attribute is essential. The format is headers="row_id column_id subcolumn_id" which links each data cell to all its applicable header IDs. This ensures screen readers announce the complete context for each cell, such as β€œNorth Region Q1 January $45,000” instead of just β€œ$45,000”.

πŸ§ͺ

Interactive Behavioral Lab

πŸ’»

Interactive Sandbox

LIVE PREVIEW

πŸ”¬ Technical Internals

Understand how this component is processed by the browser and Assistive Technology (AT). This section bridges the gap between visual code and the hidden logic that powers accessibility.

🌲 Accessibility Tree

The data structure used by screen readers to "see" your page. It translates HTML roles and attributes into standardized objects.

βš™οΈ Event Logic

Expected behavioral standards for keyboard navigation and state transitions. Crucial for users who don't use a mouse.

  • Focus: Highlights via :focus-visible
  • Activation: Responds to Enter and Space
  • Role: Identified as Complex Table

🌐 Browser & Screen Reader Compatibility

Browser
Screen Reader
Status
🍎 Safari
VoiceOver
βœ“ Safe
πŸͺŸ Chrome
NVDA
βœ“ Safe
πŸͺŸ Edge
JAWS
βœ“ Safe
🦊 Firefox
NVDA
βœ“ Safe
πŸ“± iOS Safari
VoiceOver
βœ“ Safe
πŸ€– Chrome Android
TalkBack
βœ“ Safe