CHAPTER 4 – Tables
Checking engine…

4.2 — Structuring Tables Semantically

A flat grid of rows and cells works for small tables, but real‑world data reports need clearer boundaries. By adding semantic wrappers, column definitions, and explicit header associations, you turn a simple grid into a machine‑readable database report that prints beautifully and speaks clearly to assistive technology.

Semantic Groupings: <thead>, <tbody>, <tfoot>

These three elements split your table into logical sections, mimicking a database report:

  • <thead> – the column header region. Only one per table, and it must appear first (after <caption>, if any).
  • <tbody> – the main data body. A table can contain multiple <tbody> blocks, allowing you to group related rows.
  • <tfoot> – the summary or footer region. It appears before the closing </table> but the browser renders it after the body. Only one per table.

Why it matters: When a table is printed across multiple pages, browsers automatically repeat the <thead> and <tfoot> on every sheet. In the browser, these sections can be pinned so headers stay visible while scrolling through thousands of rows.

Example with all three sections

<table>
  <caption>Q2 Revenue</caption>
  <thead>
    <tr><th>Region</th><th>Sales</th></tr>
  </thead>
  <tbody>
    <tr><td>North</td><td>$45K</td></tr>
    <tr><td>South</td><td>$32K</td></tr>
  </tbody>
  <tfoot>
    <tr><td>Total</td><td>$77K</td></tr>
  </tfoot>
</table>
Q2 Revenue
RegionSales
North$45K
South$32K
Total$77K

The visual order places the footer after the body, even though the source code may list <tfoot> before <tbody> in some older patterns. Today, the standard is thead → tbody → tfoot.

Column‑Track Architecture: <colgroup> and <col>

HTML tables are written row‑by‑row, so targeting an entire vertical column is difficult without a special element. <colgroup> and the void element <col> allow you to define column properties once, before any rows are parsed.

  • Place <colgroup> immediately after <caption>.
  • Inside it, list one <col> per column (left to right).
  • Use the span attribute on <col> to cover multiple adjacent columns: <col span="2" class="highlight">.
<table>
  <caption>Inventory</caption>
  <colgroup>
    <col span="1" style="background:#fff0e0;">
    <col span="2" style="background:#e0f0ff;">
  </colgroup>
  <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
  <tr><td>Bolts</td><td>500</td><td>$0.05</td></tr>
  <tr><td>Nuts</td><td>300</td><td>$0.04</td></tr>
</table>
Inventory
ItemQtyPrice
Bolts500$0.05
Nuts300$0.04

Column styles are applied before any cell background, so they form a base layer. This technique is perfect for colour‑coding entire columns without repeating classes on every cell.

The scope Attribute – Directional Header Binding

scope is added to <th> elements to tell assistive technology exactly which cells a header describes. The four possible values:

  • col – the header applies to all cells directly below it.
  • row – the header applies to all cells directly to its right.
  • colgroup – binds a header to an entire column group (used with <colgroup>).
  • rowgroup – binds a header to all rows within a particular <tbody>.
<table>
  <tr>
    <th scope="col">Employee</th>
    <th scope="col">Hours</th>
    <th scope="col">Rate</th>
  </tr>
  <tr>
    <th scope="row">Doe, J.</th>
    <td>40</td>
    <td>$25</td>
  </tr>
  <tr>
    <th scope="row">Smith, A.</th>
    <td>35</td>
    <td>$30</td>
  </tr>
</table>
EmployeeHoursRate
Doe, J.40$25
Smith, A.35$30

With scope, a screen reader can announce “Doe, J. – Hours: 40, Rate: 25” as the user navigates cells, making the table far more intelligible.

Complex Headers with id and headers

When a table uses irregular, multi‑tier headers (colspan/rowspan across several levels), simple scope is not enough. Instead, assign a unique id to every <th>, and then in each <td> list the IDs of all headers that apply using the headers attribute.

<table>
  <tr>
    <th id="dept" colspan="2">Department</th>
    <th id="cost">Cost</th>
  </tr>
  <tr>
    <th id="eng">Engineering</th>
    <th id="mkt">Marketing</th>
    <th></th>
  </tr>
  <tr>
    <td headers="dept eng">Staff</td>
    <td>—</td>
    <td headers="cost">$120K</td>
  </tr>
  <tr>
    <td>—</td>
    <td headers="dept mkt">Staff</td>
    <td headers="cost">$95K</td>
  </tr>
</table>
DepartmentCost
EngineeringMarketing
Staff$120K
Staff$95K

In this example, the cell “Staff” under Engineering is linked to both the “Department” top header (dept) and the “Engineering” sub‑header (eng). A screen reader can announce “Department: Engineering – Staff, Cost: $120K”. This is the most robust way to handle genuinely complex data tables.

TAKEAWAY

Semantic table structure transforms a visual grid into a logical data report. Use <thead>, <tbody>, and <tfoot> to define report sections. Use <colgroup> and <col> for column‑level styling. For simple header associations, rely on scope; for complex, multi‑tier headers, use id and headers. When you apply all these techniques, your tables become perfectly accessible, printable, and ready for any data‑heavy enterprise application.

Checking engine…