HTML Tables
When to use tables
Tables are for tabular data — information that belongs in rows and columns, like spreadsheets, schedules, or comparison charts.
Do not use tables for page layout. That was common in the early 2000s, but CSS Flexbox and Grid are far better tools for layout today.
Basic table structure
HTML
<table>
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>City</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Developer</td>
<td>London</td>
</tr>
<tr>
<td>Bob</td>
<td>Designer</td>
<td>Berlin</td>
</tr>
</tbody>
</table><table>wraps the entire table.<thead>contains header rows;<tbody>contains data rows.<tr>is a table row.<th>is a header cell (bold and centered by default);<td>is a data cell.
Spanning rows and columns
Sometimes a cell needs to stretch across multiple columns or rows:
HTML
<table>
<tr>
<th colspan="2">Full Name</th>
<th>Age</th>
</tr>
<tr>
<td>John</td>
<td>Doe</td>
<td>30</td>
</tr>
</table>colspan="2" makes the header span two columns. rowspan works the same way vertically.Adding a caption
HTML
<table>
<caption>Employee Directory — Q1 2026</caption>
<thead>
<tr>
<th>Name</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Engineering</td>
</tr>
</tbody>
</table>Captions are great for accessibility — they tell screen readers what the table is about.
Making tables accessible
- Always use
<th>for headers, not styled<td>. - Add
scope="col"orscope="row"to<th>elements so screen readers know which direction the header applies to. - Use
<caption>to describe the table.
HTML
<table>
<caption>Monthly sales by region</caption>
<thead>
<tr>
<th scope="col">Region</th>
<th scope="col">January</th>
<th scope="col">February</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">North</th>
<td>$12,000</td>
<td>$15,000</td>
</tr>
</tbody>
</table>Styling tables with CSS
Default tables look plain. Here's a clean, modern look:
CSS
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
th {
background: #f8fafc;
font-weight: 600;
}
tbody tr:hover {
background: #f1f5f9;
}border-collapse: collapse removes the double-border effect that tables have by default.Responsive tables
Wide tables overflow small screens. Common patterns:
Horizontal scroll wrapper — simplest fix:
HTML
<div style="overflow-x: auto;">
<table>...</table>
</div>Stacked rows on mobile — use CSS to display rows as cards under a breakpoint (combine with
data-label attributes on cells for column names).For very large datasets, consider whether a table is still the right UI — search, filters, or pagination often belong in JavaScript layers built on accessible HTML.
tfoot and complex headers
Use
<tfoot> for summary rows (totals, averages):HTML
<tfoot>
<tr>
<th scope="row">Total</th>
<td>$27,000</td>
<td>$31,000</td>
</tr>
</tfoot>For tables with merged headers, associate headers with cells using
id and headers when scope alone isn't enough — see the[
WCAG table tutorial](https://www.w3.org/WAI/tutorials/tables/) for complex cases.
Never use tables for page layout — CSS Grid and Flexbox handle two-dimensional and one-dimensional layout with less markup and better responsiveness. Tables are for data where row/column relationships matter to understanding the content.
Screen readers traverse tables in DOM order: row by row, left to right. Keep related columns adjacent so the relationship stays obvious when read aloud. If your table has a single row of headers and many data rows,
Simple comparison tables — two products, three features — are a sweet spot for HTML tables. Complex pivot-style layouts with merged cells in every direction are harder to make accessible; consider whether a list or definition list communicates the same information with less cognitive load.
Tables often appear in printed reports or PDF exports. A clear
Use tables only for tabular data. Structure them with
When not to use a table
Never use tables for page layout — CSS Grid and Flexbox handle two-dimensional and one-dimensional layout with less markup and better responsiveness. Tables are for data where row/column relationships matter to understanding the content.
Reading order and simple data tables
Screen readers traverse tables in DOM order: row by row, left to right. Keep related columns adjacent so the relationship stays obvious when read aloud. If your table has a single row of headers and many data rows,
scope="col" on each <th> in <thead> is usually enough. When the first column labels each row (product name, region, student), use scope="row" on those header cells so users hear "North, January, $12,000" as a coherent row rather than disconnected cells.Simple comparison tables — two products, three features — are a sweet spot for HTML tables. Complex pivot-style layouts with merged cells in every direction are harder to make accessible; consider whether a list or definition list communicates the same information with less cognitive load.
Print and export considerations
Tables often appear in printed reports or PDF exports. A clear
<caption>, visible header row styling, and zebra striping via CSS (tbody tr:nth-child(even)) improve readability on paper as well as on screen. Avoid hiding critical header text with display: none on mobile unless you provide equivalent data-label text for stacked layouts — otherwise assistive technology and print stylesheets may lose column context entirely.Key takeaway
Use tables only for tabular data. Structure them with
<thead>, <tbody>, <th>, and <td>. Add scope attributes and <caption> for accessibility. Style with CSS, not HTML attributes.