An Unfinished Symphony

It's about the internet and stuff.

Tables in the CSS Age

As CSS began to gain popularity the use of tables for layouts, quite rightly, began to lessen. However one of the side effects of this was that less developers were learning how to use and markup a table, for any purpose. Subsequently we'd often see examples of developers using nested <div>s, painstakingly positioned with CSS, being used to layout data tables which as a result fails to provide any semantic information about what is being presented. Or in some cases actual table markup used with no idea or consideration about the semantics of the elements used, which is something that I saw again only today. Given the prevalence of those scenarios I think a simple guide to laying out a data table might be helpful to some.

Before starting with the code to use we need to consider the components of a data table and why they're used:

  1. Table Summary

    This component is used to provide a summary of the structure and organisation of a data table.

  2. Caption

    The caption sits outside of the main table when viewed in the browser and provides a contextual heading describing what follows.

  3. Column Groups and Columns

    Data tables often have structurally related columns which can be defined within colgroup elements. The colgroup element can be used to define the width of the columns in the group and also, via the span attribute, the number of adjacent columns that is being defined within the group. If all columns in the table are structurally related then you can use the span to state this. If none are related you would just omit the colgroup element from your markup. The col element is used to define the properties of non-structurally related groups of columns (when used outside of the colgroup element), or to define changes in the properties of a subset of columns within a colgroup.

  4. The Table Header

    The table header can contain the headings for simple tables where the top row(s) of cells contain the column headings for the cells that follow. For more complex tables, the table header can still be used to define headings for the entire table, but as you can only have one table header section you can't define separate ones for subsection headings. The table header must contain at least one row, and the row must contain at least one cell, however the table header is optional rather than compulsory.

  5. The Table Footer

    The table footer can contain the footer data for simple tables, such as footnotes used to define or describe essential information. As with the table header, you can only have one footer section and so you can't define separate footers for subsections, only a single overall footer for the entire table. As with the table header, the footer is optional but if used it must contain at least one row, with the row containing at least one cell.

  6. The Table Body

    The title for this section shouldn't really read "the" table body as, unlike the table header and table footer, there can be more than one table body. This is useful for separating sections of a complex table into distinct groups of data rows. Each table body that is defined must, as with the table header and table footer, contain at least one row with at least one cell inside it. The table header, table footer and all table bodies must contain the same number of columns, however columns can be merged by using the colspan attribute should you need a different number of cells in a given row.

  7. Table Rows

    Table rows are used as structural containers for rows of data cells.

  8. Table Cells

    These are defined by <th> and <td> tags, where they create heading cells and data cells respectively. Headings are used to contain heading data that defines or describes the data that follows, while the data cells contain that data.

Now that we know and understand the components of a data table we can begin to structure one in a meaningful and accessible way:

The opening code for a simple data table:
  1. <table summary="This table contains information about people with the rows containing the information relating to individual people and the types of information contained in the columns.">
  2. <caption>A dummy data table containing information about pretend people</caption>
  3. <colgroup span="4" width="100"></colgroup>

The code above demonstrates an example of the opening code for a table and includes the sections that define the table, including aspects of its structure, but none of the data containing aspects. Line 1 contains the opening table tag and the summary attribute which has an explanation of how the table data is organised. Line 2 contains the caption which describes what the data is about. Line 3 contains a single colgroup element which defines the width (of the 4 columns that are a part of that group.

If you wanted to set a column to have a different width you could do so simply by including a couple of col elements nested within the opening and closing tags:

  1. <colgroup width="100">
  2. <col span="3" />
  3. <col span="1" width="250" />
  4. </colgroup>

On line 1 of the above code we have the opening colgroup tag again, however now it only contains a width attribute that defines the width of the columns in the group. Line 2 then defines the number of columns within the group that have that width setting, and line 3 defines a 4th member of the group that has a different width to the others, with line 4 closing the colgroup. Also permissable is to just use the colgroup elements as containers for col elements which are then used to define the numbers of rows, and their widths, within the colgroup, for simplicity this is the method that I most often use. For example:

  1. <colgroup>
  2. <col span="3" width="100" />
  3. <col span="1" width="250" />
  4. </colgroup>

Now that we've opened our table and defined a number of its properties we can begin to create the data containing sections:

The table header & table footer sections for a simple data table:
  1. <thead>
  2. <tr>
  3. <th scope="col">Name<sup>1</sup></th>
  4. <th scope="col">Age</th>
  5. <th scope="col">Place of Birth</th>
  6. <th scope="col">Height</th>
  7. </tr>
  8. </thead>
  9. <tfoot>
  10. <tr>
  11. <td colspan="4"><sup>1</sup> Pretend names for pretend people</td>
  12. </tr>
  13. </tfoot>

The above block of code contains the table header (<thead> on line 1, ending on line 8 with the </thead>) and table footer (<tfoot> on line 9, ending on line 13 with the </tfoot>) sections. Lines 2 & 10 and 7 & 12 define the opening and closing table row tags respectively, with lines 3-6 containing heading cells and line 11 a single data cell in the footer. Each of the heading cells contains a scope attribute, this can be set to either col or row depending on which direction it needs to be read in, here the value of col is set to define the data cells in the column below as belonging to it. I'll discuss using scope="row" in the section on the tbody later on.

For more complex tables the id and headers attributes can be used to define heading relationships between th and td cells. For information on that check the W3C section on the headers attribute, and also the axis attribute. Also, if our headings had needed to be longer and/or more complex we could have used abbreviations in conjunction with the abbr attribute.

In the table footer a single cell has been created by merging all the columns into one using the colspan attribute with a value of 1. Here a footnote, denoted by the use of a superscript #1 has been used to describe an aspect of the data in one of the heading cells.

Finally we come to the tbody (of which there may be more than one) where the data is presented:

The table body section
  1. <tbody>
  2. <tr>
  3. <th scope="row">Joe Blogs</th>
  4. <td>32</td>
  5. <td>Liverpool</td>
  6. <td>6' 1"</td>
  7. </tr>
  8. </tbody>
  9. </table>

On line 1, above, we open the first tbody section (and the only one for this example table). On line 2 we define the opening of the first table row, closing it on line 7, while on lines 3-6 we define the data cells within that row. On line 3 we have our opening data cell, which falls under the heading of Name (see the header section above) and contains the name of our example person. I've also set that data cell to be a heading cell by marking it up with <th> instead of <td>, and as this is a heading that sets a relationship with the other cells in its row it has a scope attribute with the value of row to establish this. Lines 4, 5 and 6 are simple data cells containing data that is headed by the main column headings defined in the header section earlier, but also by the row heading defined here. Finally, on line 8 we close the table body and on line 9 we close the entire table. If we put all of the component parts together we'll have the full markup for a simple data table:

The full markup for a simple data table:
  1. <table summary="This table contains information about people with the rows containing the information relating to individual people and the types of information contained in the columns.">
  2. <caption>A dummy data table containing information about pretend people</caption>
  3. <colgroup span="4" width="100"></colgroup>
  4. <thead>
  5. <tr>
  6. <th scope="col">Name<sup>1</sup></th>
  7. <th scope="col">Age</th>
  8. <th scope="col">Place of Birth</th>
  9. <th scope="col">Height</th>
  10. </tr>
  11. </thead>
  12. <tfoot>
  13. <tr>
  14. <td colspan="4"><sup>1</sup> Pretend names for pretend people</td>
  15. </tr>
  16. </tfoot>
  17. <tbody>
  18. <tr>
  19. <th scope="row">Joe Blogs</th>
  20. <td>32</td>
  21. <td>Liverpool</td>
  22. <td>6' 1"</td>
  23. </tr>
  24. </tbody>
  25. </table>

This will then be presented in the browser like this:

A dummy data table containing information about pretend people
Name1 Age Place of Birth Height
1 Pretend names for pretend people
Joe Blogs 32 Liverpool 6' 1"

Note: that table is using the table styling set in my CSS. If you were to use that example elsewhere you would need to create your own CSS to style it otherwise it would use the browser default styling. If anyone needs tips on how to do that then let me know and I can create a follow up tutorial for that. Similarly, if there's anything that needs clarification please leave a comment and ask, I'll be happy to help.

Up arrow

Comments on 'Tables in the CSS Age'

RSS feed for comments on this post.

gravatar

Great article. You make a good point, tables are seen as the 'devil' nowadays. I have designed all our websites and have implemented alot of tables in them. Mainly because of the lack of 'layers' knowledge.

There should be an article to guide oldschool guys work with 😉

gravatar

Hi Carlos, I'm not actually advocating the use of tables for layouts but for presenting tabular data. Essentially it's about using the appropriate markup for the content, in this case tables for data. They should be avoided for layouts in favour of positioning with CSS.

If you want to learn how to use CSS I can recommend Craig Grannell's book "The Essential Guide to CSS & HTML Web Design" which I technically reviewed. It'll take you through the basics and on to more advanced topics, giving you a very good understanding of using CSS for positioning.

gravatar

Tables are quick, they are simple and they work.

I agree that perhaps tables are not always the best thing, and the days of purely using tables for layouts are long gone, but they still have their uses when it comes to presenting information.

These days I live by a rule that says if I'd use a table in word, or need to use excel, then I'll use a table in the html document.

Sorry, the comment form is closed at this time.