Plate 1.7 · Phase 1 — Framing · 15 minutes
Tables, and when not to use them
A short module about an element with a strange history. Tables are exactly right for one job — data in rows and columns — and for about ten years in the 1990s and 2000s, the whole web misused them for something else entirely.
Warm-up · from Module 1.6A form submits happily, but one field's value never arrives. What's the most likely cause?
The field has no name attribute. Only named fields are sent — the name is the key the value travels under.
The one job tables do
Open the sports section and look at the NFL standings, or look at an Amtrak timetable from Boston to Washington. Every value only means something because of two things: the row it's in and the column it's in. "7" means nothing. "Wins, Philadelphia Eagles: 7" means everything.
If a value needs both its row heading and its column heading to make sense, it belongs in a table.
Schedules, price comparisons, standings, nutrition facts, opening hours by location. If it doesn't, it isn't a table — however neatly it lines up.
The anatomy of a table
<table>
<caption>Forecast highs, first week of October (°F)</caption>
<thead>
<tr>
<th scope="col">City</th>
<th scope="col">Mon</th>
<th scope="col">Tue</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Chicago</th>
<td>64</td>
<td>59</td>
</tr>
</tbody>
</table>
| Element | Means |
|---|---|
| <table> | The whole grid. |
| <caption> | The table's title — what the data is. First thing inside the table. |
| <thead> / <tbody> / <tfoot> | The header rows, the data rows, and totals at the bottom. |
| <tr> | One row. Tables are built row by row, never column by column. |
| <th scope="col"> | A heading for the column below it. |
| <th scope="row"> | A heading for the row beside it. |
| <td> | One ordinary data cell. |
| colspan / rowspan | A cell stretching across several columns or rows. Use sparingly — they make tables harder to read aloud. |
Why the headings matter: listen to a cell
A sighted reader glances up and across to find a cell's meaning. A screen reader user can't — so the screen reader does it for them, reading out the row and column headings with each value. But only if those headings are marked as headings. Click any number, then switch the headings off.
Screen reader
Click a number in the table.
Table: forecast highs, first week of October (°F).
| City | Mon | Tue | Wed | Thu |
|---|---|---|---|---|
| Chicago | 64 | 59 | 61 | 57 |
| Denver | 71 | 68 | 55 | 60 |
| Boston | 62 | 63 | 58 | 56 |
| Seattle | 58 | 57 | 60 | 59 |
| Cell | With real <th scope> | Headings as plain <td> |
|---|---|---|
| Denver, Wednesday | "Denver, Wednesday, 55" | "55" |
| Seattle, Monday | "Seattle, Monday, 58" | "58" |
Notice that real headings make the reader announce row, column, and value on its own. Without them the listener hears a bare number and must rebuild the context from memory, for every cell.
Table, or not a table?
The history lesson: layout tables
Before CSS could arrange a page properly, designers in the late 1990s discovered they could force a two-column layout by building the entire page as one giant invisible table — navigation in the left cell, content in the right. Much of the early web, from news sites to Fortune 500 homepages, was built this way.
It worked visually and was a disaster underneath: screen readers announced "table, 3 columns, 14 rows" for a homepage, content read out in nonsensical order, and nothing adapted to phones. Today layout belongs entirely to CSS — Flexbox and Grid, Modules 2.5 and 2.6. A table is for data, never for arranging a page. If you ever inherit an old site built with layout tables, now you'll recognize it.
A wide table doesn't fit a 390-pixel phone screen. The honest fix is to let the table scroll sideways inside its own container rather than squashing it — every comparison table in this manual does exactly that. You'll write that one line of CSS in Phase 2.
Three things beginners get wrong
1. Headings as <td> in bold. It looks the same and loses all the meaning — as you just heard.
2. Tables for layout. Two columns of content side by side is a layout job. That's CSS.
3. No caption. Without it, nobody knows what the numbers are before they start reading them.
Words you now own
- <tr>
- A table row.
- <th>
- A header cell, with
scopesaying whether it heads a column or a row. - <td>
- A data cell.
- <caption>
- The table's title.
- thead / tbody / tfoot
- Header rows, data rows, summary rows.
- colspan / rowspan
- A cell spanning several columns or rows.
- layout table
- The old, wrong practice of arranging a page with a table.
Add one honest table to your site.
What you should see — one way to do it
<table>
<caption>My learning plan</caption>
<thead>
<tr>
<th scope="col">Phase</th>
<th scope="col">Topic</th>
<th scope="col">Target finish</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td>HTML</td>
<td><time datetime="2026-10-15">October 15</time></td>
</tr>
<!-- one <tr> per phase -->
</tbody>
</table>
What you should see: the caption centered above, bold header cells, plain data cells — no borders yet. Browsers draw tables with no lines by default; Phase 2 adds them.
Six questions. Four correct to move on.
"If a value needs its row and its column to make sense, it's a table. Headings are th with a scope. Every table gets a caption. Tables are for data, never layout." Short module, permanent rule.