Siteimprove | HomeSiteimprove - web tools for website managers

Accessible Data Tables

Accessible Data Tables

SiteCheck , accessibility , best practices , html

Making HTML tables accessible can be a challenge. This article aims to make the process a little easier.

Summary

This article will explain two steps to create more accessible data tables. These steps are:

  1. Identify table headers.
  2. Associate table headers with data cells.

This article will explain three methods of cross referencing data cells and table headers:

  1. Using the headers attribute on table cells to reference a header id attribute.
  2. Using the scope attribute on table headers to explain the header's directionality.
  3. Using the axis attribute in table cells or headers to provide an additional layer of meta data to data in complex tables

User Agents

A User Agent is the device and/or software application that is used to access information on the internet. Such applications include screen readers, Braille displays, visual browsers such as Mozilla Firefox or Internet Explorer, or browsers that linearize text, such as the Lynx text browser or the Opera Mini browser for mobile devices.

A Note on Layout Tables

Historically tables have been applied innovatively to create visual effects and layouts that brought design to the Web. This was particularly true before CSS achieved as solid support as it has today.

Today, designing pages for the internet using layout tables cannot be justified under any circumstances. The method of using semantic HTML and Cascading Style Sheets supersedes the old-fashioned method in terms of flexibility, centralised control, and ease of maintenance. It allows lean pages of clean, well-structured content to be served, most often reducing the content volume markedly in the process.

For this reason, this article will bypass coding layout tables completely, because there is no correct method of doing this.

Data Tables

Structuring and displaying tabular data is what tables are designed to do. HTML has infrastructure in place to code extremely complex tables, cross referencing table data with the table headers that describe the data, adding metadata to similar types of table data, and much more.

However, most editing applications for the web are not geared towards properly coding tables, so often it helps to be able to troubleshoot and manually tweak the output of such editors to achieve good results. For this purpose, a knowledge of the principles of coding tables is essential.

This article assumes a basic knowledge of HTML.

The first level of table accessibility is 1) identifying table headers; and 2) making the relationship between table headers and table cells explicit.

Table headers

The starting point for all tables is the table element, which contains table rows (tr elements) and table cells (td elements).

A table might look as follows:

Row 1, Cell 1 Row 1, Cell 2 Row 1, Cell 3
Row 2, Cell 1 Row 2, Cell 2 Row 2, Cell 3

The above table is coded using a flat, non-hierarchical structure. The data in Row 1, Cell 1 is in no way related to any other data:

<table>

  <tr>
    <td>Row 1, Cell 1</td>
    <td>Row 1, Cell 2</td>
    <td>Row 1, Cell 3</td>

  </tr>
  <tr>
    <td>Row 2, Cell 1</td>
    <td>Row 2, Cell 2</td>
    <td>Row 2, Cell 3</td>

  </tr>
</table>

It is rarely the case that we have data that is completely unrelated, as the coding above suggests. For instance, it is often the case that each row or each column represents a specific type of data.

To label the data as a specific data type, we have the table header.

Table headers are represented by the HTML element th. Table headers are used similarly to table cells, in that they are contained in rows. A row may have both table cells and table headers.

A group of table headers can be contained in the thead element. Similarly, the main body of table data can reside in a tbody element.

<table>
  <thead>
    <tr>

      <th>Fruits</th>
      <th>Vegetables</th>
      <th>Animals</th>
    </tr>

  </thead>
  <tbody>
    <tr>
      <td>Orange</td>
      <td>Leek</td>

      <td>Giraffe</td>
    </tr>
    <tr>
      <td>Pear</td>
      <td>Potato</td>

      <td>Penguin</td>
    </tr>
  </tbody>
</table>

The above code is rendered as follows:

FruitsVegetablesAnimals
Orange Leek Giraffe
Pear Potato Penguin

Now we have identified the data type and given it a label. However, the label that we have applied in the th element is not yet related to the table data.

Relating table cells to table headers requires that certain cell and header attributes are used.

Table headers can take a number of attributes, the most important and relevant of which are:

  • title
  • class
  • id
  • axis
  • scope

Relationships between Table Headers and Table Cells

Making the relationship between table headers and table cells explicit can be accomplished in a few different ways. For simple data tables, defining a scope for the table header is sufficient.

Simple data table example

The data that you wish to present is conducive to the following tabular presentation:

  • A single row of Column Headers
  • Any number of Rows with data.

The visual appearance that is desired is as follows:

Caption
Header 1Header 2Header 3
Data cell 1 Data cell 2 Data cell 3
Data cell 1 Data cell 2 Data cell 3
Data cell 1 Data cell 2 Data cell 3
Data cell 1 Data cell 2 Data cell 3

Option 1: Using the headers attribute on the data cells

Such a table could be marked up using the headers attribute for the table cells to cross-reference the id attribute of the relevant column header.

<table>
<caption>Caption</caption>
<thead>

  <tr>
    <th id="h1">Header 1</th>
    <th id="h2">Header 2</th>
    <th id="h3">Header 3</th>

  </tr>
</thead>
<tbody>
  <tr>
    <td headers="h1">Data cell 1</td>
    <td headers="h2">Data cell 2</td>

    <td headers="h3">Data cell 3</td>
  </tr>
  <tr>
    <td headers="h1">Data cell 1</td>

    <td headers="h2">Data cell 2</td>
    <td headers="h3">Data cell 3</td>
  </tr>
  <tr>

    <td headers="h1">Data cell 1</td>
    <td headers="h2">Data cell 2</td>
    <td headers="h3">Data cell 3</td>

  </tr>
  <tr>
    <td headers="h1">Data cell 1</td>
    <td headers="h2">Data cell 2</td>

    <td headers="h3">Data cell 3</td>
  </tr>
</tbody>
</table>

In the example above, the table cells with the content Data cell 1 with the content Header 1 all have a headers attribute with the value h1. This value is used to cross-reference the id attribute of the table header to locate the header that applies.

Option 2: Using the colgroup element and scope attribute on the headers

Another option is to mark up the page using the scope attribute on the column headers. The scope attribute identifies the table header as either a column or a row header, and binds the information contained in data cells in the same row or column to that header.

<table>
  <caption>Caption</caption>
  <thead>
    <tr>
      <th id="h1" scope="col">Header 1</th>

      <th id="h2" scope="col">Header 2</th>
      <th id="h3" scope="col">Header 3</th>

    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Data cell 1</td>

      <td>Data cell 2</td>
      <td>Data cell 3</td>
    </tr>
    <tr>
      <td>Data cell 1</td>

      <td>Data cell 2</td>
      <td>Data cell 3</td>
    </tr>
    <tr>
      <td>Data cell 1</td>

      <td>Data cell 2</td>
      <td>Data cell 3</td>
    </tr>
    <tr>
      <td>Data cell 1</td>

      <td>Data cell 2</td>
      <td>Data cell 3</td>
    </tr>
  </tbody>
</table>

Option 3: Using the axis attribute on the data cells and headers

According to W3C, the axis attribute is used […] to place a cell into conceptual categories that can be considered to form axes in an n-dimensional space.

This allows you to add a level of conceptual information to every data item, regardless of whether this data is contained in a cell or a header.

Axes are added as comma-separated attributes

In order to demonstrate this, consider the following example. All cells and headers that have an axis defined are marked with an asterisk (*).

Read and unread mail on your Mail servers.
Read *Unread *
Buddies *Unknown *Buddies *Unknown *
mailserver1 * 12 13242 567 2345
mailserver2 * 12 15 13245 45
mailserver3 * 34321 3421 9786 345

Notice that the headers in the first column (mailserver1, mailserver2 and mailserver 3) are conceptually related: they are all the source of the emails.

Similarly the first and second header rows are each interrelated: The first row describes a Status (Read or Unread); and the second row describes your relationship with the sender.

This information could have been marked up using table headers with a column scope for the first column, and headers with row scope for the two header rows. However, using the axis attribute, we are able to add that information without cluttering the mark-up with superfluous th elements.

Additionally, this table is relatively complex, so a summary attribute should be added to the table element that briefly describes what data will be presented in the table. This enables non-visual user agents to present a synopsis of the table content akin to information that one would glean from scanning the table visually, without delving into the details of the table.

<table summary="Read and unread mail on three mail servers, split into two categories: senders that have been identified as Buddies, and unknown senders.">
    <caption>Read and unread mail on your Mail servers.</caption>
    <thead>
        <tr>
            <td></td>

            <th colspan="2" id="read" axis="status">Read</th>
            <th colspan="2" id="unread" axis="status">Unread</th>

        </tr>
        <tr>
            <td></td>
            <th id="r_fr" axis="relationship">Buddies</th>

            <th id="r_un" axis="relationship">Unknown</th>
            <th id="u_fr" axis="relationship">Buddies</th>

            <th id="u_un" axis="relationship">Unknown</th>
        </tr>
    </thead>
    <tbody>

        <tr>
            <th id="server" axis="source">mailserver1</th>
            <td headers="read r_fr server">12</td>

            <td headers="read r_un server">13242</td>
            <td headers="unread u_fr server">567</td>
            <td headers="unread u_un server">2345</td>

        </tr>
        <tr>
            <th id="server2" axis="source">mailserver2</th>
            <td headers="read r_fr server2">12</td>

            <td headers="read r_un server2">15</td>
            <td headers="unread u_fr server2">13245</td>
            <td headers="unread u_un server2">45</td>

        </tr>
        <tr>
            <th id="server3" axis="source">mailserver3</th>
            <td headers="read r_fr server3">34321</td>

            <td headers="read r_un server3">3421</td>
            <td headers="unread u_fr server3">9786</td>
            <td headers="unread u_un server3">345</td>

        </tr>
    </tbody>
</table>

Simple data table example 2

Caption
Header 1 Data cell 1 Data cell 2 Data cell 3
Header 2 Data cell 1 Data cell 2 Data cell 3
Header 3 Data cell 1 Data cell 2 Data cell 3
Header 4 Data cell 1 Data cell 2 Data cell 3

References and Further Reading

WCAG 1.0
The Guidelines of W3C's Web Accessibility Initiative for web content accessibility.
456 Berea Street on Accessible Tables
456 Berea Street provides accessibility and usability advice
Web Usability on Accessible Data Tables
Detailed information about how mark up Data tables correctly.