Avoid Spanning Table Headers

Spanned table headers are not well supported across screen readers. While you can visually style these all sorts of ways to make the spanning clear, I am focusing on the programmatic outcomes. Which essentially means how they are exposed to screen reader users.

A blonde wood dining table with a very wide crack diagonally along its length, open to nearly the full width at one end, and showing a dark wood-lined maybe extra-dimensional space within. This post uses only HTML <table>s. It does not consider ARIA table roles, partly because while ARIA has a method to span cells, there is no headers equivalent and no scope equivalent.

I made three table variations using the same structure, with the only difference in how or if it uses scope. One uses the scope attribute along with <colgroup>, <thead>, and <tbody> elements to try to provide the maximum number of programmatic cues. The second uses none of those. The third one only uses a single scope, and solely because without it the header cell could be understandably interpreted either way.

To manage expectations, I did not record any videos. Nor did I file any bugs. I simply wanted to get this documented (and maybe get feedback) and then I can try to trace the issues more specifically.

I recommend you test these yourself. I have linked resources for testing with screen readers in another post.

Testing Notes

In the lists of notes I represent column and row headers in italics using <em> and regular data cells in quotes. These tests are not exhaustive and I did not document every detail.

My testing kit:

Obviously test with the kit your users use.

1. Uses scope, <colgroup>, <thead>, <tbody>

You can visit the debug mode of this first table for easier testing.

See the Pen 1. Uses <code>scope</code>, <code>&lt;colgroup&gt;</code>, <code>&lt;thead&gt;</code>, <code>&lt;tbody&gt;</code> by Adrian Roselli (@aardrian) on CodePen.

Both Narrator and JAWS navigated diagonally (incorrectly), which is quite problematic. Otherwise, they all had some issues with header assignment, with JAWS and VoiceOver getting them wrong the most and Narrator not even announcing them.

The HTML for the first few rows:

<table>
  <caption>Books 1</caption>
  <colgroup>
    <col>
    <col>
    <col>
    <col>
    <col span="2">
    <col span="3">
  </colgroup>
  <thead>
    <tr>
      <th rowspan="2" scope="col">Region</th>
      <th rowspan="2" scope="col">Author</th>
      <th rowspan="2" scope="col">Title</th>
      <th rowspan="2" scope="col">Year</th>
      <th colspan="2" scope="colgroup">ISBN</th>
      <th colspan="3" scope="colgroup">Formats Available</th>
    </tr>
    <tr>
      <th scope="colgroup">13</th>
      <th scope="colgroup">10</th>
      <th scope="colgroup">Pulp</th>
      <th scope="colgroup">Audio</th>
      <th scope="colgroup">Digital</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Asia</th>
      <th scope="row">Murasaki Shikibu<br>(<span lang="ja">紫 式部</span>, Lady Murasaki)</th>
      <td>The Tale of Genji<br>(<span lang="ja">源氏物語</span>, Genji monogatari)</td>
      <td>1021</td>
      <td>9780142437148</td>
      <td>014243714X</td>
      <td>✔</td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <th scope="rowgroup" rowspan="7">Europe</th>
      <th scope="rowgroup" rowspan="3">Miguel De Cervantes</th>
      <td>The Ingenious Gentleman Don Quixote of La Mancha</td>
      <td>1605</td>
      <td>9783125798502</td>
      <td>3125798507</td>
      <td>✔</td>
      <td>✔</td>
      <td>✔</td>
    </tr>
    …
</table>

2. Does NOT use scope, <colgroup>, <thead>, <tbody>

You can visit the debug mode of this second table for easier testing.

See the Pen 2. Does NOT use scope, <colgroup>, <thead>, <tbody> by Adrian Roselli (@aardrian) on CodePen.

In addition to Narrator and JAWS navigated diagonally (incorrectly), NVDA has joined the chaos. Otherwise, they all had some issues with header assignment, with NVDA catching up to JAWS and VoiceOver and Narrator again not announcing them.

The HTML for the first few rows:

<table>
  <caption>Books 2</caption>
  <tr>
    <th rowspan="2">Region</th>
    <th rowspan="2">Author</th>
    <th rowspan="2">Title</th>
    <th rowspan="2">Year</th>
    <th colspan="2">ISBN</th>
    <th colspan="3">Formats Available</th>
  </tr>
  <tr>
    <th>13</th>
    <th>10</th>
    <th>Pulp</th>
    <th>Audio</th>
    <th>Digital</th>
  </tr>
  <tr>
    <th>Asia</th>
    <th>Murasaki Shikibu<br>(<span lang="ja">紫 式部</span>, Lady Murasaki)</th>
    <td>The Tale of Genji<br>(<span lang="ja">源氏物語</span>, Genji monogatari)</td>
    <td>1021</td>
    <td>9780142437148</td>
    <td>014243714X</td>
    <td>✔</td>
    <td></td>
    <td></td>
  </tr>
  <tr>
    <th rowspan="7">Europe</th>
    <th rowspan="3">Miguel De Cervantes</th>
    <td>The Ingenious Gentleman Don Quixote of La Mancha</td>
    <td>1605</td>
    <td>9783125798502</td>
    <td>3125798507</td>
    <td>✔</td>
    <td>✔</td>
    <td>✔</td>
  </tr>
    …
</table>

3. Uses Single scope; Does NOT Use <colgroup>, <thead>, <tbody>

Added scope="col" to the Author and Region header cells and scope="row" to the Asia and Murasaki header cells.

You can visit the debug mode of this third table for easier testing.

See the Pen 3. Uses a Single scope; Does NOT Use <colgroup>, <thead>, <tbody> by Adrian Roselli (@aardrian) on CodePen.

The minimal scope addition cleans up NVDA and reduces errors for JAWS and VoiceOver, but bugs persist. Narrator continues to Narrator.

The HTML for the first few rows:

<table>
  <caption>Books 3</caption>
  <tr>
    <th rowspan="2" scope="col">Region</th>
    <th rowspan="2" scope="col">Author</th>
    <th rowspan="2">Title</th>
    <th rowspan="2">Year</th>
    <th colspan="2">ISBN</th>
    <th colspan="3">Formats Available</th>
  </tr>
  <tr>
    <th>13</th>
    <th>10</th>
    <th>Pulp</th>
    <th>Audio</th>
    <th>Digital</th>
  </tr>
  <tr>
    <th scope="row">Asia</th>
    <th scope="row">Murasaki Shikibu<br>(<span lang="ja">紫 式部</span>, Lady Murasaki)</th>
    <td>The Tale of Genji<br>(<span lang="ja">源氏物語</span>, Genji monogatari)</td>
    <td>1021</td>
    <td>9780142437148</td>
    <td>014243714X</td>
    <td>✔</td>
    <td></td>
    <td></td>
  </tr>
  <tr>
    <th rowspan="7">Europe</th>
    <th rowspan="3">Miguel De Cervantes</th>
    <td>The Ingenious Gentleman Don Quixote of La Mancha</td>
    <td>1605</td>
    <td>9783125798502</td>
    <td>3125798507</td>
    <td>✔</td>
    <td>✔</td>
    <td>✔</td>
  </tr>
    …
</table>

Verdict

Unless testing with screen readers demonstrates otherwise, you can omit scope, even on spanning column and row headers, along with <colgroup>, <thead>, and <tbody>. However, you will want avoid spanning cells in general given table navigation and header announcement bugs across browsers and screen readers.

We already know that the scope attribute is unnecessary to convey column and row headers for simple tables. Steve Faulkner documented this in late 2018 with Tables and Beers and again a few days later in Tables, Tequila and Beer, and then wrapped those up just a few more days later in Short note on scoping mechanisms.

My tests were meant to see if scope would be useful on tables with spanning headers, specifically using the values colgroup and rowgroup. Mostly no, and definitely not with those two values. In the first row where the browser is not sure if the first cell of the table is a row header or column header, using scope="col" proved useful. But it took some testing to confirm that.

Of course all of this, and the buggy behavior I outlined, is mooted if you avoid spanning cells to begin with.

7 Comments

Reply

I’ve always relied on using the header attribute with the associated id when creating complex tables.

Could you just this method?

Tables with Multi-Level Headers | Web Accessibility Initiative (WAI) | W3C

Shawn Thompson; . Permalink
In response to Shawn Thompson. Reply

Doesn’t work. Unless you use “Example 3” from the W3C tutorial (which splits the table into two tables, removing the need for a headers attribute). Consider the tutorial recommends the deprecated summary attribute too.

See my third bullet under Related above.

Reply

Of course all of this, and the buggy behavior I outlined, is mooted if you avoid spanning cells to begin with.

I would have liked to see that conclusion proven. Now I’m left to imagine what the spanless example looks like and wonder how much repetition would be required to convey the same detail.

anonymous; . Permalink
In response to anonymous. Reply

I imagine that it might be different for different tables. For my fake table, I imagine you could dump the spanned row headers. For example, the region and author can be repeated in each row. The ISBN column headers I imagine need not have the word “ISBN” split up (it can be in each cell), and “Format” may not be necessary.

Alternatively, I imagine for other kinds of data you could explore splitting the tables and using headings for the kinds of column/row headers that span lots of cells. I imagine table captions can provide further clarification. I also imagine a designer and/or information architect could evaluate if all the data points are even necessary.

Grab some users and test how assorted methods compare for the data on hand and their expectations, then there will be little need to wonder how much repetition is needed.

Reply

I’m wondering if the issue with North America and Africa is because the rowspan="4" for North America when there are only three books by North American authors unless the first book by Nnedi Okorafor was written in North America.

The Africa rowspan="2" when it might need to be three.

I could be wrong about that.

john f croston iii; . Permalink
In response to john f croston iii. Reply

Yup, Nnedi Okorafor intentionally spans both North America and Africa. I don’t know where the books were actually written, I just wanted to span a row header across two preceding row headers and that seemed like a fit.

Reply

Okay, I was sure if it was by design or not.

Good luck getting things to work as expected.

john f croston iii; . Permalink

Leave a Comment or Response

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>