Creating Structured Tables
This short article (it's the example markup that makes it look long - honest!) details a the elements and attributes used to create structured tables. We'll take a simplified table and add structural and logical markup, introducing one element at a time.
The trick to creating accessible pages is to get the structure right first, then add the styling. In this article we will look at creating well structured tables that are also accessible. The styling of tables will be discussed in a later article.
Simple table
It only takes three HTML elements to create a simple HTML table.
table
is a container for all table elements.tr
acts as a container for a row of table cells.td
element defines a table cell that contains data.
With these three elements we can create a Group 9 qualification table for the European 2004 football championship. (The original table is from the BBC sports section)
<table> <tr> <td>Team</td><td>P</td> <td>W</td><td>D</td><td>L</td> <td>GF</td><td>GA</td><td>Pts</td> </tr> <tr> <td>Wales</td><td>4</td> <td>4</td><td>0</td><td>0</td> <td>10</td><td>1</td><td>12</td> </tr> <tr> <td>Italy</td><td>4</td> <td>2</td><td>1</td><td>1</td> <td>6</td><td>3</td><td>7</td> </tr> <tr> <td>Serbia & M</td><td>3</td> <td>1</td><td>2</td><td>0</td> <td>5</td><td>3</td><td>5</td> </tr> <tr> <td>Finland</td><td>4</td> <td>1</td><td>0</td><td>3</td> <td>3</td><td>6</td><td>3</td> </tr> <tr> <td>Azerbaijan</td><td>5</td> <td>0</td><td>1</td><td>4</td> <td>2</td><td>13</td><td>1</td> </tr> </table>
Headers
Our table is looking a little too sparse, let's add a bit more structure to it. Some of the data above is more than just data. For example the first row of the table contains the data "Team", "P", "W" etc. These all tell us more information about the rest of the table. They are our table data headers. Also, apart from the first row, the first column in each row ("Wales", "Italy", "Serbia & M", "Finland", "Azerbaijan") tell us that the data in the other columns of the row are all related to that country. These are also table data headers. So we should make that clear in our markup. The th
element defines a cell that contains header information, so our example markup above can be updated:
<table> <tr> <th>Team</th><th>P</th> <th>W</th><th>D</th><th>L</th> <th>GF</th><th>GA</th><th>Pts</th> </tr> <tr> <th>Wales</th><td>4</td> <td>4</td><td>0</td><td>0</td> <td>10</td><td>1</td><td>12</td> </tr> <tr> <th>Italy</th><td>4</td> <td>2</td><td>1</td><td>1</td> <td>6</td><td>3</td><td>7</td> </tr> <tr> <th>Serbia & M</th><td>3</td> <td>1</td><td>2</td><td>0</td> <td>5</td><td>3</td><td>5</td> </tr> <tr> <th>Finland</th><td>4</td> <td>1</td><td>0</td><td>3</td> <td>3</td><td>6</td><td>3</td> </tr> <tr> <th>Azerbaijan</th><td>5</td> <td>0</td><td>1</td><td>4</td> <td>2</td><td>13</td><td>1</td> </tr> </table>
Row groups: table headers, body and footers
From the table above we identified two groups of table data headers, one that described the data the fields contained (such as "Team name"), and another group that was data where it was related to other data cells in the same column. Logically, the first row of the table is separate in structure from the other rows since it contains no data, just meta data. Also, we know the team that qualifies at the top of the group automatically qualifies for Euro 2004. HTML has constructs available to describe these logical groups.
Table rows can be grouped into a table head, table foot and one or more table body sections using the elements thead
, tfoot
and tbody
. So let's go ahead and make this distinction in our markup (we'll use an id
attribute to identify the qualifier):
<table> <thead> <tr> <th>Team</th><th>P</th> <th>W</th><th>D</th><th>L</th> <th>GF</th><th>GA</th><th>Pts</th> </tr> </thead> <tbody id="qualifier"> <tr> <th>Wales</th><td>4</td> <td>4</td><td>0</td><td>0</td> <td>10</td><td>1</td><td>12</td> </tr> </tbody> <tbody> <tr> <th>Italy</th><td>4</td> <td>2</td><td>1</td><td>1</td> <td>6</td><td>3</td><td>7</td> </tr> <tr> <th>Serbia & M</th><td>3</td> <td>1</td><td>2</td><td>0</td> <td>5</td><td>3</td><td>5</td> </tr> <tr> <th>Finland</th><td>4</td> <td>1</td><td>0</td><td>3</td> <td>3</td><td>6</td><td>3</td> </tr> <tr> <th>Azerbaijan</th><td>5</td> <td>0</td><td>1</td><td>4</td> <td>2</td><td>13</td><td>1</td> </tr> </tbody> </table>
Linking table data headers to the table data
We know from the example above that certain cells are contain header information, whilst others contain just data. When seeing the above table rendered in a GUI browser it is easy to see that the header data containing the word "Pts" describes the content of all the table data in the same column. Similarly, we know just by looking at the table that the third row is data about Italy's results.
Unfortunately this seemingly simple and obvious visual identification varies from being incredibly tricky to downright impossible to do when you can't visualise the table. Since we are using GUI browsers that render tables visually, its dead-simple. But when a speech browser has to read each table element one at a time, it requires a phenomenal memory to remember what each column refers to.
To help make the table more accessible for non-visual devices, we use the scope
attributes in the table header data cells. The scope
attribute takes the following values:
row
- provides header information for the rest of the row that contains it ("everything to the right").col
- provides header information for the rest of the column that contains it ("everything below it").
On our current table we take each column of the first row and declare its scope to include the table data below it (column scope). Also since all the team names in the first column has a row scope within the table:
<table> <thead> <tr> <th scope="col">Team</th><th scope="col">P</th> <th scope="col">W</th><th scope="col">D</th><th scope="col">L</th> <th scope="col">GF</th><th scope="col">GA</th><th scope="col">Pts</th> </tr> </thead> <tbody id="qualifier"> <tr> <th scope="row">Wales</th><td>4</td> <td>4</td><td>0</td><td>0</td> <td>10</td><td>1</td><td>12</td> </tr> </tbody> <tbody> <tr> <th scope="row">Italy</th><td>4</td> <td>2</td><td>1</td><td>1</td> <td>6</td><td>3</td><td>7</td> </tr> <tr> <th scope="row">Serbia & M</th><td>3</td> <td>1</td><td>2</td><td>0</td> <td>5</td><td>3</td><td>5</td> </tr> <tr> <th scope="row">Finland</th><td>4</td> <td>1</td><td>0</td><td>3</td> <td>3</td><td>6</td><td>3</td> </tr> <tr> <th scope="row">Azerbaijan</th><td>5</td> <td>0</td><td>1</td><td>4</td> <td>2</td><td>13</td><td>1</td> </tr> </tbody> </table>
Explaining some abbreviations
Our table is typical of football league tables that we take it for granted that everyone knows about the abbreviations we use in the first row of headers. For maximum accessibility, we could replace all the abbreviations with suitable descriptions such as "Number of games played", "Number of games won", "Number of games drawn", "Number of games lost", "Goals scored", "Goals conceeded", "Total number of points". Unfortunately this makes our table very wide and untidy with long descriptions for very short one digit table data.
We can, however, supply additional description using the title
attribute on table header data. So let's update the first row of our table with an explanatory title:
<table> <thead> <tr> <th scope="col">Team</th> <th scope="col" title="Games played">P</th> <th scope="col" title="Games won">W</th> <th scope="col" title="Games drawn">D</th> <th scope="col" title="Games lost">L</th> <th scope="col" title="Goals scored">GF</th> <th scope="col" title="Goals conceeded">GA</th> <th scope="col" title="Total points">Pts</th> </tr> </thead> <tbody id="qualifier"> <tr> <th scope="row">Wales</th><td>4</td> <td>4</td><td>0</td><td>0</td> <td>10</td><td>1</td><td>12</td> </tr> </tbody> <tbody> <tr> <th scope="row">Italy</th><td>4</td> <td>2</td><td>1</td><td>1</td> <td>6</td><td>3</td><td>7</td> </tr> <tr> <th scope="row">Serbia & M</th><td>3</td> <td>1</td><td>2</td><td>0</td> <td>5</td><td>3</td><td>5</td> </tr> <tr> <th scope="row">Finland</th><td>4</td> <td>1</td><td>0</td><td>3</td> <td>3</td><td>6</td><td>3</td> </tr> <tr> <th scope="row">Azerbaijan</th><td>5</td> <td>0</td><td>1</td><td>4</td> <td>2</td><td>13</td><td>1</td> </tr> </tbody> </table>
Displaying the resulting page in a browser probably won't display any difference. However in a wide variety of browsers hovering the mouse over the top row elements reveals a tool-tip with our title attributes. Speech-based browsers will, however, will read the title for its user.
Table caption and summary
Finally, we should always indicate what our table is about. Sometimes the text before the table indicates this. We can specify a caption
for the table. This tendes to display immediately before the table. Also, we need a table summary for those non-visual browsers so visitors can decide whether to spend time going through the table with their speech-browsers or whether to skip over it.
So let's add those now:
<table summary="This is the current league table on 4th April 2003 for Group 9 of the Euro 2004 qualification cycle. It shows Wales on top with 12 points from 4 games, with Italy trailing five points behind."> <caption>Euro 2004 Qualifying Group Nine</caption> <thead> <tr> <th scope="col">Team</th> <th scope="col" title="Games played">P</th> <th scope="col" title="Games won">W</th> <th scope="col" title="Games drawn">D</th> <th scope="col" title="Games lost">L</th> <th scope="col" title="Goals scored">GF</th> <th scope="col" title="Goals conceeded">GA</th> <th scope="col" title="Total points">Pts</th> </tr> </thead> <tbody id="qualifier"> <tr> <th scope="row">Wales</th><td>4</td> <td>4</td><td>0</td><td>0</td> <td>10</td><td>1</td><td>12</td> </tr> </tbody> <tbody> <tr> <th scope="row">Italy</th><td>4</td> <td>2</td><td>1</td><td>1</td> <td>6</td><td>3</td><td>7</td> </tr> <tr> <th scope="row">Serbia & M</th><td>3</td> <td>1</td><td>2</td><td>0</td> <td>5</td><td>3</td><td>5</td> </tr> <tr> <th scope="row">Finland</th><td>4</td> <td>1</td><td>0</td><td>3</td> <td>3</td><td>6</td><td>3</td> </tr> <tr> <th scope="row">Azerbaijan</th><td>5</td> <td>0</td><td>1</td><td>4</td> <td>2</td><td>13</td><td>1</td> </tr> </tbody> </table>