Tables and frames

AUTHOR’S NOTE – You’re reading the HTML version of a chapter from the book Building Accessible Websites (ISBN 0-7357-1150-X). Copyright © Joe Clark, 2002 (about the author). All rights reserved. ¶ Back to Contents

Tables prompt eye-gouging hissyfits among accessibility advocates and Web designers of all stripes, whether oldschool or avant-garde. Both sides are saddled with myths and both argue in large part from ideology. Let’s do a reality check, shall we?

Here in the real world, you simply cannot produce Websites without tables. Don’t think you’re immune even if you have taken a vow of chastity and now do all your page layout with cascading stylesheets and sexy <div> elements (as described in Chapter15, “Future dreams”). You’ll have to get your hands dirty eventually by marking up true tabular data.


In this chapter:

The basics

Note: If you’re already familiar with basic HTML, you can skip this section.

Tables have about as many components as a jet engine. You’ve got to go slow in learning to write tables or you’ll become hopelessly lost. And you need absolute facility in basic tables in order to understand accessible table markup. This section will teach the bare bones of HTML tables, setting aside all issues of appearance.

Tables reside between <table></table> elements. The building blocks of a basic table are rows. In case you were sick that day in grade school, understand something up front: Rows are horizontal. (Columns, which have a limited identity in Web tables, are vertical. Don’t mix them up.) Rows sit inside <tr></tr> elements. tr: table row. Easy to remember.

There is no such thing as a column per se in HTML. Various tags make reference to columns, but there is no <tc></tc> tag for table columns. Instead, each table row is divided into cells. “Ah,” you think. “Table cells. <tc></tc> after all, right?” Nope: Try “table data,” <td></td>.

Each cell in a row is enclosed in <td></td>. Each row has to have at least one cell; <tr></tr> is technically illegal, while <tr><td></td></tr> is technically correct though very odd. A table can have only one cell or a theoretically unlimited number of cells.

It’s possible to nest one table within another (almost always within <td></td> elements). In fact, it’s done all the time to achieve layout tricks. The practice is horrendously complex to develop and maintain (especially if you’re coming back to a page for the first time in months) and causes slow rendering in graphical browsers.

Table rows have another variation, table headers, <th></th>. They work exactly like <td></td> but are usually found in the very first row or rows of a table. Technically, they can be placed anywhere, and indeed it’s not unknown to encounter data tables with headers specified in the middle of the table – subdivisions, you might say. Headers provide structural or conceptual information about the rows below. (If a cell acts as a header and also provides data, a difficult-to-imagine, quasi-oxymoronic combination, you must use <td></td>, not <th></th>.)

Cells can expand in height and width. You can cause a cell to occupy more than one row in height with rowspan, while you can make a cell wider than one “column” with colspan. Examples:

You can mix and match cells with different rowspans and colspans, but rows and “columns” must add up. You can use four <td></td>s in consecutive order, or <td></td><td colspan="3"></td>, or <td colspan="2"></td><td colspan="2"></td>.

That merely scratches the surface of table tags, but it's enough to get you started.

Let's also define a couple of terms:

tabular material
Information presented in rows and columns where the structure of rows and columns is essential to understanding the data.
Displaying or presenting a table for human understanding.

Tables for layout

A table is merely a grid, like a sheet of graph paper, a spreadsheet, an unplayed game of tic-tac-toe, an octothorpe, a number sign. If you can imagine stretching cells in length and width, you can imagine using tables to lay out a Web page.

Actually, you probably don’t have to imagine it: You work with layout tables every day. They’re not per se inaccessible. There’s a misconception that adaptive technology cannot read and understand tables. In fact, all major screen readers (on Windows, at least: OutSpoken on Macintosh is an exception) have specific commands for navigation inside tables. Since screen readers can also drive Braille displays, table-based layouts are accessible even to deaf-blind visitors. Layout tables pose surprisingly few access barriers.

What not to do

Absolutely none of the accessibility tags used in data tables may be used in layout tables. Technically, even summary="" is against the spec, but harmless. Do not use <colgroup></colgroup>, col, scope="", headers="", or any of the other tags fully explained in the balance of this chapter.

For layout tables, use only the most basic and unadorned <table></table>, <tr></tr>, and <td></td> elements. In fact, that is an exhaustive list of the elements you may use in coding layout tables.

Nested tables

The simple grids against which we may model HTML tables aren’t good enough for designers. We crave complex structures, which are tricky to achieve online. Impatient with the slow deployment of layouts built purely by stylesheets, in the interim we have hacked our way to complexity through nested tables.

They look nice. But there is an underside. They’re quite a bother to write, document, and maintain, especially when they’re the output of an authoring program that hasn’t been kept on a tight leash.

On the surface, nested tables do not pose an accessibility barrier. Screen readers and other adaptive technology can wade through the cells in a kind of brute-force way.

But if you’re the human being at the controls of such a device, how do you figure out exactly where you are? A sighted visitor never has this problem: You can just glance around the screen. We use layout tables for their effects, not their structure, and to sighted people, the structure is quite invisible. (That is, unless for some strange reason you have set border to a value other than zero, in which case the skeletal grid structure comes to life.)

But not everyone using the Web can see.

Understanding screen readers

The inaccessibility of nested tables is somewhat nuanced, and the best way to understand it, apart from testing your pages with a screen reader yourself, is to recall the way screen readers move around pages. Visitors using screen readers and Braille displays can chug through pages one word after another or navigate from one “item” to another, where “items” are defined by HTML markup. The commands to move through the items on a page vary from system to system, but we will model this process as akin to pressing the Tab key.

You can tab to a table and tab within a table. And here is where the problem starts. For simple layout tables not nested within other tables, it is no problem to move from cell to cell. With nested tables, though, a screen-reader user ends up working from within a maze formed by one table inside another.

Where a sighted visitor would appreciate the net appearance of all the nested tables put together, a screen-reader user navigates the underlying structure. As you know from attempting to code nested tables, the structure is damnably difficult to figure out. Now try reverse-engineering that structure via speech output.

In effect, by using nested tables, you conscript blind visitors into debugging the coding of your page by audio alone.

When you’re using a screen reader, the feedback you receive is somewhat minimalist, but also confusing. Your screen reader can, for example, read out any text or links it encounters, speak the location of the cursor, and read any table headers that may be defined. You can skip from cell to cell or row to row, and you may request an audible signal as you pass each boundary. (Not every program has all those abilities, but we’re working with a conceptual model here.)

Imagine, then, hearing dozens of audible signals as you step through the subcomponents of a Web page laid out with tables. It’s OK when each cell contains something, or if a few cells are empty here and there, but it gets a bit redundant with table nesting.

If, for example, you embed a table, comprised of one row and one cell, inside a cell within a larger table (for example, to control cellpadding and cellspacing for a specific look), passing from that outer table to the nested cell involves traversing an entire <table></table> element, plus individual tr and td tags.

Let’s take an example of a Website listing statistical reports for sale. A cell within the outer table that contains the nested table might look like this:

<td>Housing starts up 3.7% in April
<td><p><strong>In depth</strong></p>
<p><a href="/english/IPS/Data/01-004-RHS.htm" title="01-004-RHS Building Permits">Buy the full report</a> ($14.85)</p></td></tr>

To make your way to the link that lets you actually buy the report, you have to move the cursor into the outer cell, listen to the words “Housing starts up 3.7% in April,” and then, in what appears to be the middle of a sentence, your screen reader tells you a whole new table has started.

Is the cursor now in the next column or something? Why have we skipped from one table to the next?

Now, this is not even a worst-case scenario. A single-cell embedded table, while not unheard-of, is child’s play compared to what you can actually do – nest complex grids inside complex grids.

Also, in this limited example, the accessibility barrier is somewhat overstated, because not all screen readers are “verbose” in reporting the exact cell you are located in or the specific cells and rows you have moved through to get there. You can usually turn that verbosity on and off, but either way things are unclear: Even with verbosity off, a complex table nested inside another complex table will be read continuously as though there were no division between the two. With verbosity on, you hear every hiccup and burp along the route from outer table to inner table.

Not a pretty sight. Or sound.

So how do we fix it?

There has been some evolution in recent years in understanding the disadvantages of nested tables. Apart from the fact that they’re convoluted to create and maintain, the complexity they add to a page may outweigh the improved appearance that is the intended result.

Also, due to bugs in the HTML rendering of Netscape 4 (yet again, perennially), it’s often been necessary to code multiple sets of nested tables to produce a roughly equivalent visual result in the Big Two browsers. And that says nothing of supporting small-fry browsers and platforms other than Windows.

Nested tables make your pages bigger (larger in byte count, or “heavier”) and they take longer to render. Netscape 4 (encore, toujours) is the worst offender, since it won’t display anything until it works through every single cell, row, and table. Or it might partially render a page, blank the screen, and start all over again after it recomputes certain widths.

Netscape will greet you with an empty screen if you fail to close any of several table tags. While validating your pages lets you spot such errors, it may be an understatement to say that not many coders validate their pages. Certainly not every single page. The more tables you use per page, the more likely you are to commit a typo.

Irrespective of the presence or absence of tables, some browsers display text before rendering images, others the reverse. Either way you’re sitting there waiting for more information. Add that to the existing rendering complexity of tables, and you spend a lot of time waiting for pages to pop into view onscreen.

Need I mention that slow connection speeds and slow computers make matters even worse, even after all this?

I will not go so far as to recommend avoiding nested tables. Sometimes they’re necessary or desirable. However, for the love of all that is holy, please don’t design overly complex nested tables. Come up with some other way – like separate tables, or a stylesheets-only design.

Now that I’ve gotten that sermon out of the way, how do you decide if your tables are overly complex? For this purpose, we switch gears and momentarily turn our attention to Lynx.

The leading text-only browser (but not the only one), Lynx actually does interpret tables, though with the literalness and sophistication of a donkey. When people talk about linearization, they are singing the song of Lynx; nearly every screen reader can manipulate tables qua tables.

Lynx strings one cell after another in a single column. Cells arrayed horizontally in a row are unknown in Lynx. A table like this one:

<td colspan="3">Marjoram</td>
<td colspan="2">Ginger</td>

would look exactly like this in Lynx:

Thyme Rosemary Oregano
Ginger Cinnamon

Lynx lays out tables serially. Remember serial access? Lynx is a program that lets you simulate serial access in the comfort and privacy of your own office. You can test the complexity of your table layouts with Lynx. (The program runs mainly on Unix, though it has been ported to other platforms; you can also, with some difficulty, find public Lynx sites. If you run any kind of Unix box, install Lynx yourself; it’s free. Links and source code are available from

Fewer than 2% of Web-surfers of any description use Lynx. Do not take this statistic as licence to ignore Lynx users: Two percent of a million visitors is still 20,000 visitors. (Two percent of a hundred visitors is two people. Are they too few people to worry about? You decide. I say no, particularly since Basic accessibility can serve them better with next to no added effort.)

Virtually no one uses a browser that cannot understand tables at all (that is, Netscape versions prior to 2.0 or any breed of Mosaic); you can ignore this group completely.

Preliminary HTML concerns

In the next section, you will learn an exhaustive array of accessibility tags you can use with data tables. But in nearly every conceivable case, you are not allowed to use them at all for layout tables. The Web Accessibility Intitative decrees it: “If a table is used for layout, do not use any structural markup for the purpose of visual formatting.”

A couple of issues:

Data tables

I have had to undergo personal hardship to understand access tags for HTML data tables. As in all good sob stories, the hardship can be traced back to childhood. In junior high school, every student in New Brunswick was saddled with provincial aptitude tests for two days straight. I aced all of them, except a little test that showed you a set of diagrams of wee odd-shaped geometric paper grids that, when folded up, produced a solid object resembling, in retrospect, those modernist houses featured in Wallpaper.

Our task was to decide which unfolded paper grid could be assembled, origami-like, to create the single solid object pictured. I needed every second of the allotted time to finish the test, I scored a lousy 60%, and I felt like I was about to burst into tears all the way through. It was that hard.

It seems I am no good at spatial reasoning. You should watch me try to screw a cap onto something I cannot actually see. My mind simply jams.

And here I am writing an entire chapter on the only feature of HTML that demands spatial mastery, if only in two dimensions. More than enough to confuse, I say.

The good news: Even I have figured it out. The bad news: Don’t go thinking it’s easy. HTML table accessibility tags are intrinsically complex, poorly supported (making them difficult to test), and time-consuming.

So let’s start small.


You’ve got two codes available to document the table itself: summary and caption. Kooky fun fact: Even if you use both, only one will be apparent in most browsers, though you may find the Easter egg–like richness of the other code rather amusing.

A caption sits at the top or bottom of a table and provides information about it. It’s a wide-open specification: You can write whatever you want. The expected use, however, appears to follow the model of academic papers: “Figure 3b. Incidence of malaria among health-care professionals, 1965 to 1975.” That sort of thing.

The HTML spec demands precise positioning of caption: It goes right after table and before anything else. You aren’t limited to plain text: You can use so-called inline attributes like <strong></strong> and <em></em> and even img, but not block-level attributes like <p></p> or any of the hx series of headings.

Some examples:

<caption>Figure 3b<br />
<strong>Incidence of malaria among health-care professionals, 1965&ndash;75</strong></caption>
<!-- (and the rest of the table) -->

Here we snuck in a linebreak, shown by <br />. They’re perfectly legit, no matter what you might have heard.

But if this really were a table of scientific data, wouldn’t the figure label go at the bottom? Well, you can do that. caption defaults to a top placement (horizontally centred, at that), but you can select bottom.

<caption align="bottom">Figure 3b
<strong>Incidence of malaria among health-care professionals, 1965&ndash;75</strong></caption>
<!-- (and the rest of the table) -->

“Deprecated” alignment values include left and right. You’re expected to feel vaguely improper and abashed for using these and other deprecated features, and indeed they aren’t very useful. You’d think a caption aligned to the left would place the caption to the left of the whole table, like car headlights side-by-side around a grille. Nope: left and right merely cause left- or right-alignment of the caption text.

caption is a rather brute-force instrument. It attaches text to a table with the subtlety of a nailgun. Yet in data tabulation, sometimes a table requires just the sort of explication caption provides. Using stylesheets, you can improve the appearance of the caption text. Apart from the usual typographic attributes, you can also assign alignment to left or right along with the top and bottom alignments built into the caption element. Even without stylesheets, you can use oldschool HTML like small and big.

Remember the Web Content Accessibility Guidelines declaration not to use access markup in layout tables? A counterexample the braintrust at the W3C apparently failed to imagine is the use of a single-cell table to hold a photograph. In that case, the use of the caption element is warranted; the caption genuinely is a caption in that instance.

An example like this straddles the border between data and layout: We’re using the table structure to position the image, but only so that we can use the caption element in the first place. And doesn’t the image constitute the data of the table? In this case, the caption really is used to caption the photograph.

If you’re reading this book while finishing off a double espresso, the caffeine may have energized your critical faculties beyond their ordinarily awe-inducing levels. You may be tempted to slam down your cup and mutter “Why should I use this caption when I could just use another table row?” Well, you can. For any application where caption would work, so would adding a table row and typing in some text (even inside heading tags like h1 through h6).

So why use it? Structure. caption is a defined entity in HTML and adaptive technology like screen readers can seek it out and use it. A table row is merely a table row, with no defined relationship to the rest of the table.

The choice is yours, but caption is so easy and quick that, when applicable, you should use it.

But now the fun begins with summary. It’s the only attribute (or element or anything else) in all of HTML that is forbidden to manifest itself visually. The exact spec: “This attribute provides a summary of the table’s purpose and structure for user agents rendering to non-visual media such as speech and Braille.”

In other words, like sinking money into lottery tickets, you plug summary into a table and never see it again.

So why bother? (The perennial question in accessibility. I always imagine a tough-as-nails businessman [sic] in a black-and-white movie chomping a cigar, yelling at his comely secretary, and grunting “What’s in it for me?”) Because summary actually adds something useful, is dead simple, and is rather fun to deal with in certain cases.

summary is an attribute of the table element. Add it along with any other attributes, like border. For example:

<table align="center" cellpadding="5" cellspacing="3" summary="Unemployment figures in Queensland in 1994">

You can, as ever, place the summary attribute anywhere in the tag, but typing it last may make it easier to edit. The mechanics of the attribute aren’t the hard part. The hard part is writing the summary.

In the worst case (that is, in the most ill-advised case), the task may at first seem comparable to writing long descriptions, a task whose complexity you came to be all too acquainted with back in Chapter 6, “The image problem.” It seems that this worst case is espoused by the Web Content Accessibility Guidelines. Deep within the morass of Web Accessibility Initiative technique documents is this case of a summary for a sample table (which you don’t need to see to glean the complexity involved):

Total required by pollution control standards as of January 1, 1971. “Commercial category” includes stores, insurance companies and banks. The table is divided into two columns. The left-hand column is “Total investment required in billions of dollars.” The right-hand column is “Spending” and is divided into three subcolumns. The first subcolumn is titled “1970 actual in millions of dollars,” the second is “1971 planned in millions of dollars,” and the third is “Percent change, 1970 versus 1971.” The rows are industries.

Now, if your screen reader spewed that out to you and then charged forward to read out the actual meat of the table, what would you do? Shake your head in confusion? Stop the process and re-read the summary?

And if this torrent of metadata is coming at you via a Braille display (an even slower serial interface than speech, which you can at least stop, start, and skip through), how do you handle it then?

The WCAG have not decided which form of accessible table markup ought to prevail – any of the one million header tags I’ll get to shortly or this catch-all summary attribute. Looked at soberly, it’s clear that summary should be short and sweet. Why? There’s an emphasis throughout the guidelines on using actual HTML structure for accessibility rather than using circumlocutions.

It’s pretty clear: “When an appropriate markup language exists, use markup rather than images to convey information,” say the WCAG. Elsewhere, we are told: “Mark up documents with the proper structural elements.... [U]sing presentation markup rather than structural markup to convey structure (e.g., constructing what looks like a table of data with an HTML pre element) makes it difficult to render a page intelligibly to other devices.”

So why do the WCAG advocate, in the lengthy example above, the use of unstructured, unpredictable summary text to “describe” the structure of a table?

It would appear that the summary attribute exists to provide a middling level of detail. If we think back to making images accessible, summary is possibly comparable to adding a title, not a longdesc. Imagine a page containing multiple tables. A screen-reader user might use the summary attribute to differentiate one table from another. (Admittedly, the comparison with image accessibility falls down here, because we used longdesc as a differentiator in that case.)

On the upside, summarys are quite easy to write. Just ask yourself “What is this table for?” and write that down in a concise sentence. It might require superhuman imagination to write a summary that doesn’t begin with the words “This table...” or “A table that...” but we can live with it. Indeed, given all the other metadata that could be enunciated by a screen reader, a consistent writing structure might help keep summary separate from title or header information.

If summary is meant to be invisible in graphical browsers, how can it be “fun” nonetheless, as I promised before? Because some graphical browsers can actually speak. iCab on Macintosh is one of them, and it will dutifully read out a summary. You can impress your friends: Have them over to demonstrate a browser that talks without additional hardware or software and watch their eyebrows raise when the system utters words that simply are not present onscreen. Certainly, for the purposes of impressing your friends, I could not possibly suggest that you load up the summary attribute with song lyrics, Bible passages, or one of the raunchier personal ads in the alternative newsweekly of your choice. That would be irresponsible, and would simply require you to remove the fake text and use the summary properly, lest you inadvertently foist upon the world a table with summary text you’d prefer that strangers never hear.

Headers, headers, and more headers

All right. The party’s over. Now we hit the head-scratchers.

HTML tables make provision for header information. In fact, they make bewildering, multilayered provision for headers. Over and over again, in so many confusing ways whose intentions and application are poorly explained in World Wide Web Consortium source documents.

To make matters worse, even some screen readers don’t understand all the header substructures in HTML tables. Learning every nook and cranny of HTML table headings prepares you for the future more than it solves today’s accessibility problems.

What is a header?

HTML specs are a tad vague on what a header actually is, how many of them there can be, what forms they can take, and how one form can be differentiated from another, and how they interact with related accessibility structures like groupings.

So it behooves us to come up with a functional definition. Headers use structured representations to elucidate or introduce tabular data. Headers usually precede data; a header that follows data can be called a footer.

Why “structured”? Because we’re using HTML, which enforces several kinds of structure. Why “representations” and not words? Because nothing stops you from using images (like pictographs or product photography), with appropriate alternate texts and titles, as your headers; they’re meant to be read by human beings, whose comprehension is not limited to written words. (Or you may also be stuck using a picture of, say, Japanese text rather than actual Japanese text because you lack fluency, the right fonts, or the expertise to type in Japanese.)

Why “elucidate or introduce” rather than just the latter? Because tables are two-dimensional: Your header information may sit in two places in the table, like the very top and the extreme left, and a reader must combine the two to understand the data. Or, in another case, you might look at the middle of a table, starting with a cell containing a number; then read up to find the Unemployment column header; read to the left to find the Queensland row header; and then, at last, understand that the number combines with the headers to mean unemployment in Queensland.

But that of course is the problem. A screen-reader or Braille user cannot simply glance at the row and column headers. Remember, those are serial-access devices, and in the worst case you’d have to traverse every intervening row or column until you hit the header, then step all the way back to where you started. Now, adaptive technology can automatically associate header information with cells, if you provide it. And you have a range of options. Let’s start small.

First-level headers

Remember that table rows, marked up by <tr></tr>, contain table cells or table “data,” specified by <td></td>? You also have the option of using <th></th> instead of <td></td> for cells in a row that are meant to act as headers for the “column” implied by the table structure.

It’s permissible and encouraged to use <th> on cells in the middle of a table if those cells act as headers for the rows below them but not the rows above. You can also use a single <th> cell (like the first in a row) as a row header.

Second-level headers

There is, however, another level of header abstraction in tables, known, surprisingly enough, as <thead></thead> for table head.

Actually, in a strict interpretation, HTML tables contain a <thead></thead>, a <tbody></tbody> (the body or meat of the table, and there can be more than one), and a <tfoot></tfoot> (a footer). You are not required to use any or all of those codes. Indeed, <tbody></tbody> is implicit in every table (even an empty one: <table><tr><td></td></tr></table> is legal, and the empty row is the body). By contrast, headers and footers must be explicitly specified.

To remain kosher, you will need to get in the habit of explicitly marking up <tbody></tbody> if you also use <thead></thead> or <tfoot></tfoot>. If you’re making a header or footer explicit, you must not leave the body implicit.

Again in strict interpretation, <thead></thead> is intended for visual presentation. If your table extends beyond one printed page, every page can show the table header. The spec states: “This division enables user agents to support scrolling of table bodies independently of the table head and foot. When long tables are printed, the table head and foot information may be repeated on each page that contains table data.”

Adaptive technology can nonetheless read and use the <thead></thead> information. Or if it cannot, it needs to be upgraded by the manufacturer; you’re doing your part and they need to do theirs.

What goes inside <thead></thead>? Rows and cells. (“The table head and table foot should contain information about the table’s columns. The table body should contain rows of table data,” saith the W3C.) And of course those rows can also be headers, if you recall the <th></th> or table-header element. Headers inside headers? Yup. And it gets better: You can use as many <tbody></tbody> divisions as you want.

So we’ve got tables with headers inside headers and one or more bodies. Welcome, ladies and gentlemen, to HTML for tables.

Easy example:

<table align="center" cellpadding="5" cellspacing="3" summary="Unemployment figures in Queensland in 1994">
<tr><th>City</th><th>Age</th><th colspan="3">Rate (%)</th></tr>
<!-- Rows and cells in any quantity -->


Footers are footers but also headers, in the way that a thumb is a thumb but also a finger or a leg is a leg but also a limb. Footers are merely headers that appear at the bottom rather than the top.

There is, in fact, an HTML construct for footers, which goes by the unsurprising name of <tfoot></tfoot>. Yet table footers, in the real Web as it is genuinely experienced as contrasted with the parallel-universe Web envisioned by the World Wide Web Consortium, are rarely necessary.

In the prehistoric medium called print, true table footers tended to limit themselves to tables in which headers and footers needed to be the same to make understanding the table easy. The canonical example is a printed transit timetable, with departure cities listed in a left-hand column and destination cities in header and footer. That way, even if your departure city were two-thirds of the way down a very long table, you could trace down to the destination city rather than traversing that two-thirds distance all the way back to the top. In some cases, departure cities were listed not only in a column at left but the rightmost column for similar reasons. (All this applies, of course, to someone who can actually see, read, and manipulate a printed timetable.)

We could describe footers of this sort as structural. It is clear that the HTML table spec envisages this sort of structural footer, which is intended to appear at the bottom of successive pages were a lengthy table actually printed out. Structural footers, then, are more or less equivalent to the running footers of conventional print typography.

Now, you may be tempted to imagine that, say, the totals of a financial table, by virtue of appearing at the bottom of said table, are actually footers. That is rarely the case, if it ever is, because the totals represent one-time-only information that will not be repeated on later pages (or screenfuls, if online). One could imagine running subtotals appearing on each page or screenful, but there is no HTML mechanism for such a thing. (This isn’t print, with its standardized dimensions. How do you determine the length of your visitor’s screen? If you don’t know even that much, how do you place running footers?) Running subtotals are hard to display even using a spreadsheet program.

In any event, footers are rarely encountered in Web tables. There’s not a lot to know about them; the chief concern is placement of the <tfoot></tfoot> code. Let’s quote the W3C: “tfoot must appear before tbody within a table definition so that user agents can render the foot before receiving all of the (potentially numerous) rows of data.”

Yes, you have to list the end of a table before the middle of it, like so:

<table align="center" cellpadding="5" cellspacing="3" summary="Unemployment figures in Queensland in 1994">
<tr><th>City</th><th>Age</th><th colspan="3">Rate (%)</th></tr>
<tr><th>City</th><th>Age</th><th colspan="3">Rate (%)</th></tr>
<!-- Rows and cells in any quantity -->

Note that the spec tells us to place <tfoot></tfoot> before <tbody></tbody>; it says nothing about where to place <thead></thead> in relation to <tfoot></tfoot>, so you might as well place it first for logic's sake.

In this example, we’re duplicating the headers in the footer for easy scannability by a sighted person.

Does all of this have any relevance for screen-reader users?

Not a whole lot. In fact, as far as a screen reader is concerned, footers might as well not exist, except in the surpassingly rare cases where footers provide information that headers or body rows do not. (Remember, we’re talking about true footers here, not unstructured, unrepeated text that coincidentally appears at the bottom.) It’s like expecting a fly to be able to see, let alone care about, the soaring peak of a mountain. Footers are simply beyond the consideration of screen readers, which, as we know all too well, are sequential devices. Knowing about what appears at the bottom of a table isn’t of much use to a machine that reads from the top down.

This argument does not excuse you from coding table footers correctly should you blaze a trail and actually write a table that requires them. They’re just not something you particulary need to worry about in day-to-day Web design.


HTML provides a bewildering array of header tags and attributes below the level of <thead></thead> and <th></th>. The list includes:

We need to hack our way, as if with a machete, through this thicket of definitions by relying on structure at all times.

As we learned in Chapter 7, “Text and links,” it is quite possible to specify a heading (not a header, a heading) as ordinary text typeset in a large bold font such that any typical sighted visitor would immediately recognize it as a heading. A screen reader, being as dumb as a sack of hammers, cannot read your mind and understand that a line of text marked up with <font face="Arial" size="+3"><bold></bold></font> (to use a particularly garish example that is common in more ways than one) is actually intended as an introduction to the next block of text.

If, however, you avail yourself of one of the six header tags of the <hx> series, screen readers will get what you mean. If you format that enclosed text with a stylesheet, you can produce more or less whatever look you want (and, in fact, an improved appearance compared to the brute-force <font></font> element).

As HTML authors, we are at least passingly familiar with a mental model of text that includes six levels of headers. We are not, howeover, accustomed to a mental model of tables that includes groups of rows and of columns and a practically unlimited set of headers that must be explicitly associated with them.

Vertical groupings

Columns are neglected stepchildren in HTML tables. There is no specific tag for a table column (there’s only <td></td> for table data; compare this with <tr></tr> for table row), yet you have a ton of options for clumping related columns together.

The first of these is <colgroup></colgroup>, which does just what it says: It groups columns. In defining your table, you can allocate as many <colgroup></colgroup>s as you want. It’s a confusing tag that smacks of being thrown in at the last minute.

In the way that every table has a <tbody></tbody>, whether listed explicitly or not, every table has an implicit <colgroup></colgroup>. But if you add that tag yourself, you can specify how many columns are encompassed by that grouping and apply certain formatting options to them in one fell swoop.

Stick your <colgroup></colgroup> element or elements right after the <table> opening tag itself (and after a <caption></caption>, if you’re using one). Use span="" to specify how many columns are part of that group. You can set up as many <colgroup></colgroup>s as you need to structure the information the table conveys.

The following are all legal examples and all apply to a table with a total of eight columns.

It is vaguely disturbing that the opening and closing tags can contain nothing at all yet be entirely valid.

Indeed, column groupings give you a kind of magisterial power to impose structure on a table. But we’re not done yet.

Columns within column groups

Uniformity is so twentieth-century. What makes us think that every column within a group will be exactly the same?

There is, in fact, provision for diversity within unity through the use of the col element. The duckbill platypus of table tags, it’s even stranger than <colgroup></colgroup>: Among other things, you are forbidden to use a closing tag. (Accordingly, in XHTML you must close col with a space and a slash.)

col elements sit inside <colgroup></colgroup>s. It has essentially the same function as <colgroup></colgroup>: It lets you identify columns (in any number, using the span="" attribute) and treat them specially.

Why, then, don’t we simply nest <colgroup></colgroup> elements the way we can nest lists? If we’re trying to achieve recursion in structuring columns, why not set things up so that the higher-level element (<colgroup></colgroup>) recurs?

Beats me. The differences between <colgroup></colgroup> and col are pedantic at best and merely add to the confusion. But we are stuck with this confusion.

To use the span="" attribute: span="1" is implicit unless otherwise specified, and <col /> is the same as <col span="1" />. I have shied away from documenting the innumerable table formatting attributes through this chapter, but I will note that you can set the alignment of the contents of cells with cellhalign and cellvalign (note the oddball spellings).

Actually, in that case, now we see a way in which <colgroup></colgroup> and col differ: You can set alignment for a column group and override it individually with <col>. We could fondly imagine doing this with nested <colgroup></colgroup>s, but we do not live in that imaginary dreamworld.

To recast an example from above:

<col span="1" width="35" />
<!-- Explicitly specifying a single column 35 pixels across (above) -->
<colgroup span="3" halign="left">
<col />
<col cellhalign="center" />
<col />
<!-- Centre the middle column only -->
<colgroup span="3">
<col span="3" />
<!-- Redundant, but legal -->

These column groupings are quite the bother. And, at time of writing, the Big Two authoring programs, GoLive and Dreamweaver, cannot insert them automatically for you. So you’re stuck typing them in yourself. An appetizing prospect, isn’t it?

scope and headers

For users of adaptive technology, “associating” headers with table cells is necessary to interpret the table. A great deal of effort has gone into defining HTML structures for this purpose.

It may be heretical to note that reading tables through speech or Braille is a pain in the arse in the first place. Correct header coding merely reduces the chance that reading a table will be a pain in the arse and you won’t be able to understand the table even after all your efforts.

We can learn a few things from old media here. In the talking-book field, tables are read aloud by a human being. One approach, which I’ve actually used myself, is to provide a reader’s note explaining what the top and left headers are (in one common example), then to read the rows one at a time.

It’s a good system, really. I don’t even need to show you how such a table would look. It is so self-documenting that you can figure out the structure just from this simulation of reading a table in talking-book format:

You don’t even have to bother envisioning how a table with this information would look because the information is right there in an understandable form.

But in talking books, a kind of information-searching purpose is uncommon, and even when it happens it’s not very difficult compared to the Web. Most of the time, talking-book listeners do not particularly care about what any specific cell of a table actually says. They are not on the hunt for particular information. If, say, you’re a scientist and you do actually care, then it is a simple matter of sitting there and waiting until the right information is read to you. Admittedly, this is a bother if the table consists of an alphabetical list of country names with related data and the country you’re interested in is Zimbabwe, but one can always fast-forward and rewind talking books. It’s a low-tech, slow-moving, genteel medium, optimized for light usage.

On the Web, however, it is much more likely that you’re interested in one specific table cell with its specific data. You the reader must take active steps to find and read that cell. This of course requires that your adaptive technology support accessible table tags, which, at time of writing, is far from universal.

In any event, the relatively simple HTML scope element duplicates the genteel talking-book approach. When you add scope to a table cell, you designate that cell as having scope over a column or row – in other words, that it is a form of header.

So, to set up a column header, add scope="col" to any table-header cell (<th></th>) or even to a normal cell (<td></td>). Voilà: You have now told the world that the column of cells below that cell has the scoped cell as header. This tidy solution is muddied somewhat by the fact that HTML lacks a true conception of columns; they are defined by inference. (It’s as though the World Wide Web Consortium realized its mistake and later gave us a scope attribute that explicitly designates a cell as a column header, but was unable to rewrite the old HTML spec to include columns per se. Understandable, but inelegant.)

Somewhat more usefully, you can designate a cell in a row as the header for that row. Yes, finally headers can extend horizontally. Just add scope="row" to that cell.

This remarkably easy and effective approach takes almost no time at all. How would we code the talking-book example above?

<th scope="col">Province</th>
<th scope="col">Males 18–35</th>
<th scope="col">Males 36–51</th>
<th scope="col">Females 18–35</th>
<th scope="col">Females 36–51</th>
<td scope="row">Nova Scotia</td>
<td scope="row">New Brunswick</td>

The column headers, then, are: Province, Males 18–35, Males 36–51, Females 18–35, Females 36–51. The row headers are Nova Scotia and New Brunswick.

Simple, huh?

You can vary the scope="" attribute a little. You can also use scope="rowgroup" or scope="colgroup". The latter is self-explanatory; scope applies to whatever colgroup immediately encloses it. But there is no such thing as a rowgroup per se in HTML. Instead, scope="rowgroup" applies to “the remaining cells of the <thead></thead>, <tfoot></tfoot>, or <tbody></tbody>,” according to the HTML specification. (Here we are yet again with the asymmetry between rows and columns in HTML, except here it’s the rows that are not true defined structures.)

It is not clear how often structures like these will come up in actual use. How often does a cell have horizontal scope on groups of columns? Horizontal scope on groups of rows is somewhat easier to envision, I suppose.

However, we have a problem. We run across multiple rows of headers all the time. I routinely see two-layer headers, and I have seen three-layer headers in scientific papers. We’re dealing with heavily qualified data sets of this sort (setting aside every header tag for clarity):

<td colspan="8">Males</td>
<td colspan="8">Females</td>
<td colspan="4">Nova Scotia</td>
<td colspan="4">New Brunswick</td>
<td colspan="4">Nova Scotia</td>
<td colspan="4">New Brunswick</td></tr>
<td>Grade school</td>
<td>High school</td>
<td>Grade school</td>
<td>High school</td>
<td>Grade school</td>
<td>High school</td>
<td>Grade school</td>
<td>High school</td>

The scope attribute handles one level at a time – a single column, a single row, or a single rowgroup or colgroup. HTML does have a mechanism for setting up networks of headers and cells, but it is a pox on all our houses.

The headers of our current example could look like this (adding a few other headers while we’re at it – note the transformation of td to th):

<th colspan="8" id="males">Males</th>
<th colspan="8" id="females">Females</th>
<th colspan="4" id="males-ns">Nova Scotia</th>
<th colspan="4" id="males-nb">New Brunswick</th>
<th colspan="4" id="females-ns">Nova Scotia</th>
<th colspan="4" id="females-nb">New Brunswick</th></tr>
<th id="males-ns-grade">Grade school</th>
<th id="males-ns-hs">High school</th>
<th id="males-ns-uni">University</th>
<th id="males-ns-post">Postgraduate</th>
<th id="males-nb-grade">Grade school</th>
<th id="males-nb-hs">High school</th>
<th id="males-nb-uni">University</th>
<th id="males-nb-post">Postgraduate</th>
<th id="females-ns-grade">Grade school</th>
<th id="females-ns-hs">High school</th>
<th id="females-ns-uni">University</th>
<th id="females-ns-post">Postgraduate</th>
<th id="females-nb-grade">Grade school</th>
<th id="females-nb-hs">High school</th>
<th id="females-nb-uni">University</th>
<th id="females-nb-post">Postgraduate</th>

Note that every header cell’s id must be obsessively microdetailed.

Oh, but it gets worse. Now we deal with the data.

<td headers="males males-ns males-ns-grade">
<td headers="males males-ns males-ns-hs">
<td headers="males males-ns males-ns-uni">
<td headers="males males-ns males-ns-post">
<td headers="males males-nb males-nb-grade">
<td headers="males males-nb males-nb-hs">
<td headers="males males-nb males-nb-uni">
<td headers="males males-nb males-nb-post">
<td headers="females females-ns females-ns-grade">
<td headers="females females-ns females-ns-hs">
<td headers="females females-ns females-ns-uni">
<td headers="females females-ns females-ns-post">
<td headers="females females-nb females-nb-grade">
<td headers="females females-nb females-nb-hs">
<td headers="females females-nb females-nb-uni">
<td headers="females females-nb females-nb-post">

And that’s just one row.

For n layers of table headers, each data cell must carry n custom-added headers="" references. The example above is not atypical; there will be tremendous confusing redundancy at every level of exposition. True, the headers="" codes are not meant for human reading, but you are a human and you have to create them.

The case can be made, however, that the example above is the most complex way to code the headers. The most specific id of each row is the last one, so the other two could be left out:

<td headers="males-ns-grade">
<td headers="males-ns-hs">
<td headers="males-ns-uni">
<td headers="males-ns-post">
<td headers="males-nb-grade">
<td headers="males-nb-hs">
<td headers="males-nb-uni">
<td headers="males-nb-post">
<td headers="females-ns-grade">
<td headers="females-ns-hs">
<td headers="females-ns-uni">
<td headers="females-ns-post">
<td headers="females-nb-grade">
<td headers="females-nb-hs">
<td headers="females-nb-uni">
<td headers="females-nb-post">

Tedious nonetheless, don't you think?

And, moreover, adaptive-technology support for this coding is essentially nonexistent. Does this mean that complex tables with multi-layered headers are essentially inaccessible no matter what you do? Yes.

So why bother?

Indeed, why bother?

What we need is for authoring programs (spreadsheets among them, very much including Excel) to insert these headers="" codes automatically. It ain’t happening yet. Then we need adaptive technology to really use them. That ain’t happening yet, either.

I would recommend, then, adding these tedious id/headers="" codes when you are able. Always use the scope attributes; they are easy to add, even by hand.

As support for the tediously overdetailed id and header="" coding increases, so can your use of it.


One quickie point. On the <th></th> element only, you may add an abbr="" attribute, which, not surprisingly, means “abbreviation.”

It’s strictly optional. The purpose is to abbreviate an especially long header so that, if you’re stuck listening to it a dozen times during table readout, you will be less likely to yearn for a quick and merciful death. This provision does seem to imply that the adaptive technology will read out all the other headers (in their many shapes and forms) so you can fully understand the context. It also seems to imply that adaptive technology would be smart enough to read the full form the first time and the abbreviated form thereafter.

It is my experience, though, that verbose table headers are verbose for a reason – it takes that many words to express the concept and/or you need that many words to differentiate the header from other headers. It is not immediately obvious how one abbreviates these headers meaningfully. If it were actually possible, we wouldn’t write a long header in the first place. And, in the print medium, people already use abbreviated headers anyway, often expanding the abbreviation in a footnote. (For that purpose, we would enclose the abbreviation in the <abbr></abbr> element. Note the difference, and see Chapter 7, “Text and links.”)

In the case of short headers, I suppose trimming “Meeting date” to “Date” is harmless, but barely worth the trouble.

The abbr="" attribute on the <th></th> element seems to be a character in search of an author, really. Use it if you wish, but it is optional.


Frames do not elicit quite the eye-gouging hissyfits that tables do, at least among accessibility experts (self-proclaimed or otherwise). The big knock against frames is their usability – frame-based pages are hard to bookmark, slow to load, and difficult to program, and tend to insist on being displayed in windows of a certain minimum size.

Nonetheless, the leading screen readers (i.e., not OutSpoken for Macintosh) can all handle frames. They are not per se inaccessible, and the accessibility additions are rather modest.

Note, though, that as with tables, moving around inside frames with a screen reader adds levels of complexity. Generally, you are informed whenever you traverse a frame boundary. If frames also enclose tables, you get to hear warnings about tables, too.

If the big knock against frames is usability, this knock gets even bigger when accessibility is considered. My recommendation is not to use frames unless you have no other way of achieving your layout.

The basics

Note: If you’re already familiar with basic HTML, you can skip this section.

Frames themselves are merely HTML documents. You specify frames inside a larger container document that must, by the way, carry a so-called Frameset DOCTYPE, one of which, for XHTML, is as follows:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"

In the grand tradition of using the same words over and over in HTML (abbr! header!), after declaring a Frameset DOCTYPE you immediately use a different kind of <frameset></frameset>. That is the element that surrounds however many frame elements you’re using (as many as 16, in my experience). It also surrounds the critical <noframes></noframes> content. You have a great many formatting attributes at your disposal (to set width and height of frames, for example), which you can look up yourself.

<frameset cols="200, *">
<frameset rows="75%, *">
<frame src="frame_left.html" name="Navbar" title="Navigation" />
<frame src="frame_botleft.html" name="Search" title="Search the site" />
<frame src="main.html" name="Main" title="Main body" />
You need a frames-capable browser to enjoy our site to the utmost. However, our <a href="main.html" title="Main body">
main content</a>
is still available.</p>

The approach is rather similar to nested tables (and vaguely reminiscent of <colgroup></colgroup> and col) in that framesets contain frames or other framesets.

A frame element simply links to an HTML document (through src="filename"). You must assign a name (and optionally an id – here’s another case where id and name are not interchangeable); a title is useful for accessibility, as we’ll see shortly.

Also inside <frameset></frameset> you must – and I really mean must – include a <noframes></noframes> element, which can contain <body></body> elements or any block-level element below that level, like paragraphs and headings. As the name implies, your noframes content will be seen by browsers that cannot render frames (e.g., Lynx). In fact, here in the real world, the only device you’d ever run across that cannot handle frames is Lynx. Yes, we do get the wireless zealots who insist we not forget people browsing Websites on cellphones, but this theoretical activity does not actually happen in the real world; if people surf anything at all on a tiny mobile device, they surf special wireless-friendly Website variants.

Exactly what to put inside <noframes></noframes> is no longer very clear. In the days of simple Websites, you could duplicate nearly all the main content, where “main content” was defined as whatever wasn’t a navbar or a search box or some other fixed entity that appeared across multiple pages. (Or you could define “main content” as the homepage, the default page served up when someone surfs to your domain.)

Back then, you could get away with that approach if you also duplicated any frame-based navbars in plain text (or accessible imagemaps, I suppose) inside the main content itself. You could add a search function, too. The fact that it was even possible to carry out such duplication added fuel to usabilitistas’ fire: Why bother with frames if you can fit all your markup inside a normal HTML document?

Complex database-driven Websites very often have complex database-driven navbars (and other edge-of-screen frame content) along with generated main content. So what do you put inside <noframes></noframes>?

I do not have an answer, or not a very good one. At the very least, include a sitemap if you’ve got one, a search function, and an E-mail address or a reply form directed to your Webmaster. <noframes></noframes> content, while necessary, is a bit outdated and is certainly noncritical.

What you absolutely must not place inside the noframes tag is anything remotely resembling the following:

In other words, do not tell people to get lost until they upgrade to equipment you consider acceptable.

Accessible frames

The World Wide Web Consortium added only a couple of attributes to the <frame></frame> element, one of which is simple and makes sense, the other quite the opposite.

As with essentially everything else you can imagine in HTML, you can add a title to your frame element. You’re already required to include a name, so the distinction between the two resembles the alt/title distinction in graphics. Use the name attribute for an indication of basic function (Main, LeftNav, Search, Footer, Contact) and add a title for a fuller explanation (Company homepage, Navigate our site, Search, Copyright and credits, Contact us):

<frame src="main.html" name="Main" title="Company homepage" />
<frame src="leftnav.html" name="LeftNav" title="Browse our site" />
<frame src="search.html" name="Search" title="Search" />
<frame src="copyright.html" name="Footer" title="Copyright & credits" />
<frame src="contact.html" name="Contact" title="Contact us" />

As in the case of Search, the two texts don’t always have to differ.

What you could argue about is the use of a longdescription. Yes, for some unfathomable reason the esteemed Consortium has equated longdesc for images with longdesc for frames. The actual spec for longdesc tells us: “Describe the purpose of frames and how frames relate to each other if it is not obvious by frame titles alone.” Why do you need to drone on and on and on about the function of a frame?

There is no set limit on the length of a frame title (or any title), though 1,024 characters (1 K) of title text is the recommendation I have made for alt and title on images. Yet if you cannot differentiate two frames in 1 K of text, accessibility is not your problem; confusion, overcomplexity, and lack of usability are. longdesc is not going to help you.

The Web Accessibility Initiative prompts a bitter little chuckle when it suggests using longdesc to describe the appearance of frames. I could not think of anything of less interest to a visitor who cannot see frames. This suggestion appears to be a failed analogue of a description of the appearance of an entire page, another hobbyhorse the WAI loves to ride (see Chapter 6, “The image problem”). Think about it: Does a screen-reader user or someone browsing with Lynx particularly care what your page or, worse, your frame happens to look like? I doubt it. By definition, overall appearance of this sort is irrelevant to such visitors. Ignore the WAI’s advice to longdescribe frame appearance.

If you really must use a longdesc, remember that it is simply an HTML file. Every technical recommendation in the use of longdesc for images applies here – naming conventions ( framename-LD.html), use of anchors, and the like. (Have a look back at Chapter 6, “The image problem.”) But really, this does seem a bit de trop.

In summation

Tables are a bother. Until authoring programs and adaptive technology conveniently support the full HTML spec, it is sometimes not worth the bother. Nonetheless, there are enough accessible table tags that are well-supported and easy to use that you are now well equipped to noticeably improve the accessibility of your tables even without titanic effort.

To make frames accessible, use <noframes></noframes> and title, and think carefully about usability; don't use frames unless you truly have to.

Bottom-Line Accessibility Advice

Basic accessibility

Intermediate accessibility

Advanced accessibility

Previous   ¶   Contents   ¶   Next