Sections (<thead>, <tbody>, <tfoot>)
Large tables benefit from being split into three logical sections: a header, a body, and an
optional footer. <thead>, <tbody>, and <tfoot> group rows semantically, give you clean CSS
styling hooks, and — critically for printed or paginated tables — let the header repeat on
every page.
Basic Structure
<table>
<thead>
<tr>
<th scope="col">Order #</th>
<th scope="col">Customer</th>
<th scope="col">Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>1001</td>
<td>Maria Chen</td>
<td>$84.20</td>
</tr>
<tr>
<td>1002</td>
<td>James Okafor</td>
<td>$32.10</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row" colspan="2">Total</th>
<td>$116.30</td>
</tr>
</tfoot>
</table>Section | Purpose |
|---|---|
| Column header row(s) — appears once, describes the columns |
| The actual data rows — a table can have multiple |
| Summary/total row(s) — totals, averages, or footnotes |
<thead>, <tbody>, then <tfoot>. Interestingly the HTML spec also allows <tfoot> to appear right after <thead> (before the body) in markup, and browsers still render it visually last.Multiple <tbody> Groups
A single table can contain several <tbody> elements. This is useful for visually and
semantically grouping related rows — for example, splitting a product catalog table by
category, while keeping it all one table (one set of column headers).
<table>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
<tr><th scope="rowgroup" colspan="2">Laptops</th></tr>
<tr><td>UltraBook 14"</td><td>$1,299</td></tr>
<tr><td>ProBook 16"</td><td>$1,899</td></tr>
</tbody>
<tbody>
<tr><th scope="rowgroup" colspan="2">Monitors</th></tr>
<tr><td>27" 4K Display</td><td>$449</td></tr>
<tr><td>34" Ultrawide</td><td>$799</td></tr>
</tbody>
</table><tbody> groups makes it easy to target a specific group with CSS (tbody:nth-of-type(2)) or JavaScript, without adding extra wrapper markup that would break the table structure.CSS Styling Hooks
Because <thead>, <tbody>, and <tfoot> are real elements, they're natural, low-specificity
CSS selectors — no extra classes needed for common patterns like sticky headers or a
visually distinct footer.
thead {
background-color: #1976d2;
color: white;
}
thead th {
position: sticky;
top: 0; /* header stays visible while scrolling a tall table */
}
tfoot {
font-weight: bold;
border-top: 2px solid #333;
}
tbody tr:hover {
background-color: #f0f4ff;
}Printing Implications: Repeating <thead>
When a long table spans multiple printed pages, browsers automatically repeat the{' '}
<thead> content at the top of each new page — but only if you actually used{' '}
<thead>. A table built only from a bare first <tr> (no <thead> wrapper) will not repeat,
leaving later pages with unlabeled columns.
@media print {
thead {
display: table-header-group; /* ensures repeat-on-each-page behavior */
}
tfoot {
display: table-footer-group;
}
}<thead>. Skipping it is one of the most common reasons a "print this table" feature looks broken on page 2 onward.Key Takeaways
thead, tbody, and tfoot group table rows into header, body, and footer sections.
DOM order is thead, then tbody, then tfoot — the browser renders tfoot last visually regardless of where it appears relative to tbody in markup.
A table can contain multiple tbody groups to organize related rows without extra wrapper markup.
These sections are natural CSS selectors for sticky headers, alternating row styles, and footer emphasis.
Using <thead> is what makes column headers repeat automatically across printed pages.