Tables are for data, not layout
Reach for a table only when you have information that lives in a grid of rows and columns. For page layout, use Flexbox or Grid instead.
Good for tables
Pricing plans, class schedules, sports stats, spec sheets, financial reports — anything you'd put in a spreadsheet.
Not for layout
Don't use tables to position columns, sidebars, or cards. That's what CSS layout is for.
Rule of thumb
If the data has a natural header for each column or row, a table is the right tool.
The building-block tags
Every table is made from a small set of nested elements. Learn these six and you can build any table.
A basic table
Rows are built left to right with <tr>, and each cell is a <td>. The header row uses <th>.
| Name | Role | City |
|---|---|---|
| Ada Lovelace | Engineer | London |
| Grace Hopper | Admiral | New York |
| Katherine Johnson | Mathematician | Hampton |
HTML
<table>
<thead>
<tr><th>Name</th><th>Role</th><th>City</th></tr>
</thead>
<tbody>
<tr><td>Ada Lovelace</td><td>Engineer</td><td>London</td></tr>
<tr><td>Grace Hopper</td><td>Admiral</td><td>New York</td></tr>
<tr><td>Katherine Johnson</td><td>Mathematician</td><td>Hampton</td></tr>
</tbody>
</table>
Caption, totals, and spanning cells
Add a <caption> for a title, a <tfoot> for totals, and use
colspan to merge a cell across columns. rowspan does the same down rows.
| Region | January | February |
|---|---|---|
| North | $4,200 | $5,100 |
| South | $3,800 | $4,400 |
| Total | $8,000 | $9,500 |
HTML
<table>
<caption>Q1 Sales by Region</caption>
<thead>
<tr><th>Region</th><th class="right">January</th><th class="right">February</th></tr>
</thead>
<tbody>
<tr><td>North</td><td class="right">$4,200</td><td class="right">$5,100</td></tr>
<tr><td>South</td><td class="right">$3,800</td><td class="right">$4,400</td></tr>
</tbody>
<tfoot>
<tr><td>Total</td><td class="right">$8,000</td><td class="right">$9,500</td></tr>
</tfoot>
</table>
<!-- "right" right-aligns numbers (see the CSS in Step 3).
colspan merges a cell across columns; rowspan does it down rows: -->
<td colspan="2">Spans two columns</td>
Make it readable with CSS
Raw tables are cramped. border-collapse merges the double borders, padding adds breathing room,
and a zebra stripe with :nth-child(even) makes long tables easy to scan.
CSS
table { border-collapse: collapse; width: 100%; }
th, td {
border: 1px solid #d2d2d7;
padding: 10px 14px;
text-align: left;
}
thead th { background: #06b6d4; color: #fff; }
/* Zebra striping */
tbody tr:nth-child(even) { background: #f5f5f7; }
/* Right-align numbers */
td.right { text-align: right; }
<div style="overflow-x:auto"><table>…</table></div>
Accessible tables
A few small habits make tables work for screen-reader users too.
Use real headers
Mark header cells with <th>, never a bold <td>. Add scope="col" or scope="row" so each header is tied to its column or row.
Always caption
A <caption> gives the table a name that's announced before the data.
Keep it a grid
Avoid merging cells unless the data truly needs it — complex spans are hard to navigate.
The whole thing as one file
Everything above — a captioned, accessible, zebra-striped data table with a totals row — combined into one complete HTML file. Copy it into a new file called tables.html, open it in a browser, and it just works — no libraries, no build step.
Live preview
Complete HTML — copy this whole file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Q1 Sales by Region</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: #1d1d1f; background: #f5f5f7; line-height: 1.6;
padding: 40px 20px;
}
.page { max-width: 640px; margin: 0 auto; }
h1 { font-size: 1.8rem; margin-bottom: 6px; }
.intro { color: #6e6e73; margin-bottom: 24px; }
/* Wrap wide tables so they scroll instead of breaking small screens */
.table-wrap { overflow-x: auto; background: #fff; border-radius: 12px; padding: 20px; }
table { border-collapse: collapse; width: 100%; font-size: .95rem; }
caption {
caption-side: top; text-align: left; font-weight: 700;
font-size: 1.05rem; padding-bottom: 12px;
}
th, td {
border: 1px solid #d2d2d7;
padding: 10px 14px;
text-align: left;
}
thead th { background: #06b6d4; color: #fff; }
/* Zebra striping makes long tables easy to scan */
tbody tr:nth-child(even) { background: #f5f5f7; }
tfoot td { font-weight: 700; background: #eef6f8; }
/* Right-align numbers */
.right { text-align: right; }
</style>
</head>
<body>
<div class="page">
<h1>Quarterly Report</h1>
<p class="intro">Regional sales for the first quarter, with monthly totals.</p>
<div class="table-wrap">
<table>
<caption>Q1 Sales by Region</caption>
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col" class="right">January</th>
<th scope="col" class="right">February</th>
<th scope="col" class="right">March</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">North</th>
<td class="right">$4,200</td>
<td class="right">$5,100</td>
<td class="right">$5,600</td>
</tr>
<tr>
<th scope="row">South</th>
<td class="right">$3,800</td>
<td class="right">$4,400</td>
<td class="right">$4,900</td>
</tr>
<tr>
<th scope="row">West</th>
<td class="right">$5,500</td>
<td class="right">$6,000</td>
<td class="right">$6,300</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td class="right">$13,500</td>
<td class="right">$15,500</td>
<td class="right">$16,800</td>
</tr>
</tfoot>
</table>
</div>
</div>
</body>
</html>