
This is the seventh article in the Semantic (X)HTML Markup series. Before we begin, you'll want to read the previous articles:
In this article we'll learn how to use perhaps the most misused semantic element: the table element. Like all the other (X)HTML elements we've learned about, there's a right and wrong way to use tables. The W3C created the HTML table model to "arrange data — text, preformatted text, images, links, forms, form fields, other tables, etc. — into rows and columns of cells." They specifically state that tables are not to be used for layout:
Tables should not be used purely as a means to layout document content as this may present problems when rendering to non-visual media. Additionally, when used with graphics, these tables may force users to scroll horizontally to view a table designed on a system with a larger display. To minimize these problems, authors should use style sheets to control layout rather than tables.
The accessibility problems of layout tables are why avoiding tables for layout is checkpoint 5.3 of the Web Content Accessibility Guidelines (WCAG). Using tables for layout can also rob you of one of the greatest benefits of CSS: its flexibility. Using CSS, the entire look of a site can be changed with a few edits to one style sheet. If complicated, nested tables were used instead, creating even minor layout changes can become a huge undertaking.
In recent years, many web developers have begun listening to the guideline to avoid layout tables and now use CSS to lay out their web pages. Instead of fitting sections of the page into rigid table grids, this new layout method involves placing content (marked up with semantic headings, paragraphs, and lists, of course) into div elements for each section of the page and then using CSS to position and style these divs.
Unfortunately, many forgot that tables still have a valid and valuable place in web design and tried to get rid of tables in their designs altogether. This is not the correct approach either. The table is still a valid (X)HTML element, and when you are trying to mark up tabular data, it is incorrect to use anything else!
Since the Semantic (X)HTML series is focused on how to use and mark up semantic elements, not about how to not use certain elements, this article will focus on the proper use of tables for data rather than on how to create layouts without tables. For information on using CSS instead of tables to lay out your pages, start with Flowing and Positioning: Two Page Models, Float: The Theory, and The Box Model Problem. Once you understand these concepts, you should be ready to undertake a CSS-based layout, such as the one detailed in Stephanie Sullivan's Creating Fluid Pages (Part Two).
So tables should still be used to mark up tabular data, but what is tabular data? Tabular data is information that has a two-dimensional relationship. Let's take a simple data table:
| Title | Name |
|---|---|
| CEO | John Smith |
| Vice President | Jane Doe |
| Executive Secretary | Mary Thomas |
The data in this table has relationships in both the horizontal (title-name pairs) and vertical (lists of titles or names) directions. Although the values do not have to be laid out in columns and rows, they make sense in columns and rows. When you are trying to decide whether or not to use a table for a section of content, think about whether you could put it into Excel or another grid-based program. Regardless of whether or not you want it to appear in a grid at the end, could it fit in a grid with descriptive headers labeling both rows and columns? Notice also that tabular data doesn't have to be numeric. It can be any sort of text or even images, as long as it has a two-dimensional relationship with the data around it.
If you've just read the Structuring Lists or Styling Lists articles in this series, you may be tempted to use a definition list to mark up the data shown in the table above. While I agree that a definition list would be acceptable for this example, I think a table is a better choice in this case. This is because of the two-dimensional relationship that the table provides. As a table, you could read it across, pairing the names with the titles, or you could read down the columns to read the entire list of titles and names separately. Using a definition list, your data is arranged in only one dimension – you must read a title, then a name, a title, then a name, instead of being able to read down the lists. All you have is the pairs, not the separate lists of names and titles. A table creates richer relationships between the data so that user agents can interpret it and read it in various ways.
Conversely, there are times when a list might be better suited to your data, as the previous articles on lists illustrated. It's up to you to evaluate the relationships between your data and decide which (X)HTML element is most well-suited to your content.
If you've used tables at all in the past, either for layout or data, you probably know that they are made up of rows, designated with the tr element, and cells, designated with the td element. However, there is more to a table than just rows and cells. (X)HTML contains a whole host of table elements to add greater structure to your tables, which in turn makes them more accessible and easy to style.
The example tables given in this section are also included in a separate document named table-headers.htm, included in the downloads for this article. Feel free to open it in Dreamweaver so you can view the full markup for each of the tables as we go along.
In addition to the td element, there is also the th element. The th element is another kind of table cell, but instead of holding data, it holds header information. The th element is important to include because it makes the distinction between header cells and data cells clear even in the absence of CSS or other visual aids. They also allow screen readers to make large tables of data more intelligible to their users. Screen readers read tables in a linear fashion, which means that they read across rows, starting at the top left cell. Take the following table:
| Food | Calories | Fat | Carbs | Protein |
| McDonald's Big Mac | 600 | 33 | 50 | 25 |
| Wendy's Big Bacon Classic | 580 | 29 | 45 | 33 |
| Burger King Original Whopper | 700 | 42 | 52 | 31 |
| Arby's Arby-Q | 360 | 11 | 51 | 18 |
If you wanted to know the amount of carbs in a Whopper, you could locate the appropriate column and row, scan across to see where they intersect, and read the value in that cell. Someone using a screen reader, however, would have to listen to the table read in a linear manner, so he or she might hear something like this:
"Food Calories Fat Carbs Protein McDonald's Big Mac 600 33 50 25 Wendy's Big Bacon Classic 580 29 45 33 Burger King Original Whopper 700 42 52 31 Arby's Arby-Q 360 11 51 18."
Imagine trying to figure out the amount of carbs just by having all that read to you. In anything other than the simplest tables, identifying values can become quite difficult for non-visual users. This is why Section 508 and the Web Content Accessibility Guidelines (WCAG) dictate that row and column headers should be identified for all data tables. To designate table header cells, simply change the td tags of the cells in question to th tags.
Assistive technology can then use these headers to identify the data in the cells to their users. For instance, if the user navigated to the cell in the fourth row and fourth column, instead of just hearing "52," the screen reader might announce "Carbs Burger King Original Whopper 52." This helps the user know what the data means.
You can also specify an abbreviated header name using the abbr attribute of the td or th element. This can be helpful to screen reader users so they don't have to hear the full header announced over and over again. For instance, you might set abbr="Whopper" on the "Burger King Original Whopper" cell. The user would still hear the full name when the table is first read through, but subsequent announcements would use the abbreviated form. Obviously, abbreviated headings are not needed for all header cells, but can be helpful on those with longer header information. Including the abbr attribute is WCAG Checkpoint 5.6 (Priority 3).
Once you have marked up the appropriate cells with th tags, you need to associate the data cells with the appropriate header cells. There are two ways to do this: scope and headers.
The scope attribute should be used on simple data tables such as the one above. It is added to each opening th tag and specifies whether the th is a header for the column, column group, row, or row group. Adding ths and scope to the above table produces the following markup (abbreviated to save space; see table-headers.htm for full markup):
<table>
<tr>
<th scope="col">Food</th>
<th scope="col">Calories</th>
<th scope="col">Fat</th>
<th scope="col">Carbs</th>
<th scope="col">Protein</th>
</tr>
<tr>
<td scope="row">McDonald's
Big Mac</td>
<td>600</td>
<td>33</td>
<td>50</td>
<td>25</td>
</tr>
...
</table>
Note that I have left the cells containing the names of the foods as tds but still assigned the scope attribute to each of these cells. This is because these cells do not contain header information – they contain the names of the foods, which are actual values in our data, not just generic labels like "food" or "calories." Thus, they should remain marked up as tds. However, they also provide context for the information in the rest of the row, so they have been assigned the scope attribute of "row." (X)HTML allows you to add the scope attribute to either th or td elements as necessary.
If you look at the table after changing the markup, you'll see that the display has also changed. Most browsers render text inside ths as centered and bold (the CMX style sheet gives them a blue background, but this is not the default presentation). If you don't like this, you can easily change it using CSS. For instance, the CMX style sheet gives th elements a blue background with dark blue text, via this simple selector:
#articlecontent th {
background-color: #CEDBED;
border-bottom: 1px solid #036;
color: #036;
font: .9em Arial, Verdana, Geneva, Helvetica, sans-serif;
font-weight: 500;
padding: 4px;
}
Alternately, if you do want centered or bold text, don't just use a th element to achieve this formatting if your cell is not truly a header cell. Use the appropriate type of cell, then use CSS to style it visually how ever you like.
In more complex tables, the header information for a given cell might not be in the same row or column as that cell, or the cell might have more than two headers that apply to it. In this case, you need to use the headers attribute instead of scope to associate the data cell with its header cells. You do this by assigning an id to each of the header cells and then listing the ids of the applicable header cells in the headers attribute of the td.
I'll modify our original table to create a table with two levels of headers.
| Food | Calories | Fat | Carbs | Protein |
|---|---|---|---|---|
| Wendy's Big Bacon Classic | 580 | 29 | 45 | 33 |
| hamburger patty | 200 | 13 | 0 | 19 |
| bun | 200 | 2.5 | 38 | 7 |
| Burger King Original Whopper | 700 | 42 | 52 | 31 |
| hamburger patty | 260 | 20 | 0 | 21 |
| bun | 250 | 4 | 45 | 8 |
If you look at the value "2.5" in the fourth row and third column (highlighted in yellow), for instance, note that you cannot determine its heading information from the headers in its row and column alone. These headers indicate that it is a fat count (column header) for a bun (row header), but you also need to know that it is for the Wendy's Big Bacon Classic, which is not in the same row or column as the value. The headers attribute provides a way to identify all three header cells. The markup for the new table is shown below (abbreviated).
<table>
<tr>
<th id="food">Food</th>
<th id="calories">Calories</th>
<th id="fat">Fat</th>
<th id="carbs">Carbs</th>
<th id="protein">Protein</th>
</tr>
<tr>
<td id="wendys">Wendy's Big Bacon Classic</td>
<td headers="calories wendys">580</td>
<td headers="fat wendys">29</td>
<td headers="carbs wendys">45</td>
<td headers="protein wendys">33</td>
</tr>
<tr>
<td id="patty1" headers="wendys">hamburger patty</td>
<td headers="calories patty1 wendys">200</td>
<td headers="fat patty1 wendys">13</td>
<td headers="carbs patty1 wendys">0</td>
<td headers="protein patty1 wendys">19</td>
</tr>
<tr>
<td id="bun1" headers="wendys">bun</td>
<td headers="calories bun1 wendys">200</td>
<td headers="fat bun1 wendys">2.5</td>
<td headers="carbs bun1 wendys">38</td>
<td headers="protein bun1 wendys">7</td>
</tr>
...
</table>
The top row of cells is still marked up with th elements, but the scope attributes have been removed in place of id attributes. The first column of cells also contains id attributes. Each data cell then contains a headers attribute that lists the ids of the header cells that provide its heading information. So, for our highlighted cell, you will notice that it contains three space-separated values in its headers attribute: fat, bun1, and wendys. This tells the user agent that those three cells provide heading information for that cell.
You'll notice that the markup for the headers attribute example is far more complicated and heavy than the scope attribute example. For this reason, you should always use scope when the table allows. Consider ways to reorganize your table that will allow you to use scope in place of headers when ever possible. For instance, the same information could have been presented in the following table instead:
| Food | Calories | Fat | Carbs | Protein | |
|---|---|---|---|---|---|
| Wendy's Big Bacon Classic | total sandwich | 580 | 29 | 45 | 33 |
| hamburger patty | 200 | 13 | 0 | 19 | |
| bun | 200 | 2.5 | 38 | 7 | |
| Burger King Original Whopper | total sandwich | 700 | 42 | 52 | 31 |
| hamburger patty | 260 | 20 | 0 | 21 | |
| bun | 250 | 4 | 45 | 8 | |
In this table, the heading information for each cell is in the same row, column, row group or column group, so scope could be used. Here is how the markup looks (abbreviated):
<table>
<tr>
<th colspan="2" scope="colgroup">Food</th>
<th scope="col">Calories</th>
<th scope="col">Fat</th>
<th scope="col">Carbs</th>
<th scope="col">Protein</th>
</tr>
<tr>
<td rowspan="3" scope="rowgroup">Wendy's Big Bacon
Classic</td>
<td scope="row">total sandwich </td>
<td>580</td>
<td>29</td>
<td>45</td>
<td>33</td>
</tr>
<tr>
<td scope="row">hamburger patty</td>
<td>200</td>
<td>13</td>
<td>0</td>
<td>19</td>
</tr>
<tr>
<td scope="row">bun</td>
<td>200</td>
<td>2.5</td>
<td>38</td>
<td>7</td>
</tr>
...
</table>
You can provide a name or title for your table using the caption element. The caption element is placed immediately after the opening table tag.
<table>
<caption>Nutrition Information for Fast Food Sandwiches</caption>
...
The caption appears centered above the table by default. Previously, the placement of the caption could be controlled by the align attribute, but this has been deprecated in favor of the caption-side property of CSS, which you can set to top, bottom, left, or right. You can also use CSS to change any other properties of the caption that you wish, such as text alignment, boldness, and color.
Internet Explorer does not support caption-side at the time of this writing, so your caption will always appear above the table in that browser.
Though captions are not required for all tables, adding them to your tables is a good practice to get into to assure the purpose of your table is clear.
To provide a more detailed description of your table, you need to use the summary attribute on the table. While summary is not required for simple tables, it can help non-visual users understand more complicated data tables and is especially important for tables that don't have captions. Including the summary attribute is WCAG Checkpoint 5.5 (Priority 3). The W3C states that the summary attribute "provides a summary of the table's purpose and structure." It can clarify the relationship between cells that might not be obvious to non-visual users as well as describe how the table relates to the context of the current document. Examples of table summaries given by the W3C include:
You may also want to use your summary to highlight important points in the data. For instance, you might have a table that charts declining automobile crashes over several years along with several other pieces of data. Visual users might not have trouble recognizing the trend of decline in crashes because they can look at the table as a whole and quickly compare values across an entire row or column. Non-visual users, however, have to listen to the data piece by piece, so it might be more difficult for them to recognize the larger trend that some of the numbers illustrate among all the other values that are being read off to them. A good summary for a table like this might be:
<table summary="This table charts the amount of automobile crashes in Springfield from 1998 to 2004 and gives the number of fatalities, number involving alcohol, and number involving pedestrians. The number of automobile crashes has declined each year since 2000, with corresponding drops in number of fatalities.">
Remember, though, that the content of the summary attribute is not made visible, so if the information you want to include is really important, you may want to include it in the text of your page itself instead so that everyone can benefit from it.
Although some accessibility advocates recommend providing a summary for every table, others (myself included) feel that unnecessary summaries do more harm than good. If the information in your table is sufficiently clear from the caption, headers, and data itself, providing a summary just adds complexity and lengthens the amount of time it takes to the user listen to the page being read and get to the information he or she wants. The summary attribute should always be used on complex tables, but is unnecessary otherwise.
First, a quick summary:
Now that you know how to structure your tables in a way that provides the most information to all your users, you probably want to know how to format them visually while still maintaining a separation between content and presentation. This article has just given you a taste of how you can use CSS to alter the default presentation of table elements. In the next article, we'll cover how to replace traditional HTML table formatting methods with CSS properties to create any look for your tables that you want.
Keywords
semantic, (X)HTML, XHTML, markup, tables, tr, td, th, headers, scope, abbr, captions, summary, accessibility, screen reader, screenreader, data, rows, columns, cells