Tables and Structured Data
Proper table markup for data, not layout
HTML tables present two-dimensional data with rows and columns. They are the correct tool for tabular data — schedules, financial figures, comparison grids — but should never be used for page layout (that is what CSS Grid and Flexbox are for).
Basic Table Structure
A well-structured table separates its head, body, and foot using semantic section elements.
<table>
<caption>Q1 Sales by Region</caption>
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">Units Sold</th>
<th scope="col">Revenue</th>
</tr>
</thead>
<tbody>
<tr>
<td>North</td>
<td>1,240</td>
<td>$62,000</td>
</tr>
<tr>
<td>South</td>
<td>980</td>
<td>$49,000</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td>2,220</td>
<td>$111,000</td>
</tr>
</tfoot>
</table>
Spanning Cells
colspan and rowspan let a single cell occupy multiple columns or rows.
<table>
<thead>
<tr>
<th rowspan="2">Name</th>
<th colspan="2">Scores</th>
</tr>
<tr>
<th>Math</th>
<th>Science</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>95</td>
<td>88</td>
</tr>
</tbody>
</table>
Accessibility
Screen readers navigate tables by headers. Use scope on every <th> and <caption> or aria-label on the table. For complex tables with multi-level headers, use id and headers attributes to explicitly associate cells.
<!-- Simple table: use scope -->
<th scope="col">Product</th>
<th scope="row">January</th>
<!-- Complex table: use id + headers -->
<th id="h-q1">Q1</th>
<th id="h-north">North</th>
<td headers="h-q1 h-north">$42,000</td>
Styling Tables with CSS
table {
border-collapse: collapse; /* removes double borders */
width: 100%;
}
th, td {
border: 1px solid #ddd;
padding: 0.75rem 1rem;
text-align: left;
}
thead th { background: #f5f5f5; font-weight: 600; }
/* Zebra striping */
tbody tr:nth-child(even) { background: #fafafa; }
/* Sticky header on scrollable table */
thead th { position: sticky; top: 0; z-index: 1; }
Responsive Tables
Wide tables overflow on small screens. Wrap the table in a horizontally scrollable container rather than reflowing the table structure.
<div style="overflow-x: auto; -webkit-overflow-scrolling: touch;">
<table>
<!-- wide table content -->
</table>
</div>
colgroup for Column Styling
<table>
<colgroup>
<col style="width: 40%">
<col style="width: 30%; background: #fffbeb">
<col style="width: 30%">
</colgroup>
<!-- ... -->
</table>
Best Practices
- Always use
<caption>— it is the table's accessible name - Use
<th>for headers, never<td>styled to look like a header - Keep tables simple — if a cell needs a nested table, reconsider the data model
- Sort and filter large tables with JavaScript, not by re-rendering HTML