Tables

Build a data table that a screen reader can navigate and a phone can display.

10of 22
≈ 11Min read
7Parts

A table is for data

Tables are for information with two dimensions — rows and columns that both mean something. They are not a layout tool; that has been CSS grid's job for years.

A table anyone can read needs three things beyond the data: a <caption>, real <th> header cells, and scope saying whether each header heads a column or a row.

a complete table
<table>
  <caption>Lead times by finish</caption>
  <thead>
    <tr>
      <th scope="col">Finish</th>
      <th scope="col">Stock</th>
      <th scope="col">Made to order</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Painted</th>
      <td>5 days</td>
      <td>3 weeks</td>
    </tr>
    <tr>
      <th scope="row">Veneer</th>
      <td>10 days</td>
      <td>5 weeks</td>
    </tr>
  </tbody>
</table>

The parts

ElementWhat it is
<caption>The table's name and its accessible name. Must be the first child.
<thead> <tbody> <tfoot>Row groups. tfoot is for totals and may be written last.
<tr>A row.
<th>A header cell. Takes scope="col", "row", "colgroup" or "rowgroup".
<td>A data cell.
colspan / rowspanHow many columns or rows the cell covers.
<colgroup> / <col>Lets you size or style whole columns at once.

Why scope matters

With scope, a screen reader can announce a cell together with its headers — "Veneer, made to order, 5 weeks" — instead of a bare "5 weeks" with no idea which row or column it belongs to. It costs one attribute per header.

Complicated tables

If headers span in both directions, scope is not enough: give each <th> an id and each <td> a headers="id1 id2". If you need that, first ask whether two simpler tables would be better.

Spans

Every row must still add up to the same number of columns once spans are counted. A miscount silently shifts the rest of the table.

a cell over two columns
<tr>
  <th scope="row">Total</th>
  <td colspan="2">15 days</td>
</tr>

Sizing columns

table-layout: fixed in CSS makes the browser use the first row and your column widths instead of measuring all the content — much faster on a long table, and it stops one long word widening a column.

colgroup
<table>
  <colgroup>
    <col style="width: 40%">
    <col span="2">
  </colgroup>
  …
</table>

Tables on a phone

A wide table will not fit a small screen, and shrinking the text is not a fix. Put the table in a container that scrolls sideways on its own, inside a page that does not. Give the container a tabindex so it can be scrolled by keyboard, and a label so it is announced.

a scrollable table
<div class="tablewrap" tabindex="0" role="region" aria-label="Lead times">
  <table>…</table>
</div>

Numbers

Right-align numbers and use tabular figures so the digits sit in columns. It makes a table of prices readable at a glance.

columns of figures that line up
td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

Practice