HTML thead, tbody and tfoot
Look at a bank statement or an invoice. At the top, a header row names the columns. In the middle, line after line of details. At the bottom, a totals row that adds everything up. Three clear regions, each with its own job.
HTML lets you describe those regions with three elements: <thead>, <tbody>, and <tfoot>. A table works without them, but with them it becomes better organized, easier to style, and clearer to assistive technology. They are a small amount of extra code for a big gain in quality.
What You Will Learnβ
- What
<thead>,<tbody>, and<tfoot>mean - The correct order of table sections
- How to build a totals row
- How to use several
<tbody>groups - How to style each section, and keep headers visible
- Why sections help accessibility and printing
- Common mistakes
The Three Sectionsβ
| Element | Name | Holds |
|---|---|---|
<thead> | Table head | The header row(s): column labels |
<tbody> | Table body | The main data rows |
<tfoot> | Table foot | Summary rows: totals, averages, notes |
Each one wraps one or more <tr> rows. They do not change how the rows look by default. They change what the rows mean.
- HTML Code
- Browser Output
<table>
<caption>Monthly expenses (USD)</caption>
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Rent</th>
<td>800</td>
</tr>
<tr>
<th scope="row">Food</th>
<td>300</td>
</tr>
<tr>
<th scope="row">Transport</th>
<td>90</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>1,190</td>
</tr>
</tfoot>
</table>
| Item | Amount |
|---|---|
| Rent | 800 |
| Food | 300 |
| Transport | 90 |
| Total | 1,190 |
Browsers always draw <thead> rows at the top and <tfoot> rows at the bottom of the table.
The Order of Table Partsβ
When all the parts are present, they go in this order inside <table>:
<table>
<caption> 1. caption (optional)
<colgroup> 2. column groups (optional)
<thead> 3. header section (optional)
<tbody> 4. one or more bodies
<tfoot> 5. footer section (optional)
</table>
tfoot positionOlder versions of HTML required <tfoot> to come before <tbody>, so browsers could draw the footer early on long, streamed tables. Current HTML places <tfoot> after the <tbody> sections, which is far more natural to read. Use the modern order, and you will be fine.
The Rulesβ
| Rule | Details |
|---|---|
<thead> and <tfoot> | At most one of each per table |
<tbody> | One or more |
| Contents | Only <tr> rows |
| Row cells | Normal: <th> and <td> |
- Invalid
- Valid
<table>
<tbody>...</tbody>
<thead>...</thead> <!-- head after body -->
<tr>...</tr> <!-- loose row among sections -->
</table>
<table>
<thead>...</thead>
<tbody>...</tbody>
<tfoot>...</tfoot>
</table>
If you use any section element, put all of your rows inside one. Mixing loose <tr> elements with sections is not allowed.
<thead>: The Headβ
<thead> holds the rows that label the columns. Most tables have one header row, but you can have several (for example, a group label above the column labels).
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Role</th>
</tr>
</thead>
What <thead> gives you:
- Meaning. Tools know these rows are headings, not data.
- A styling hook.
thead th { ... }styles the entire header at once. - Repeating headers. When a long table prints on several pages, browsers can repeat the header on each page.
- Sticky headers. Easy to keep the header visible while scrolling (see below).
<tbody>: The Bodyβ
<tbody> holds the main data rows.
<tbody>
<tr>
<th scope="row">Aisha</th>
<td>Developer</td>
</tr>
<tr>
<th scope="row">Mateo</th>
<td>Designer</td>
</tr>
</tbody>
Remember from Table Basics that browsers add a <tbody> for you if you leave it out. Writing it yourself is a good habit. It makes your code match what the browser builds, and it avoids surprises with CSS selectors like tbody tr.
Several <tbody> Groupsβ
A table can have more than one <tbody>. Use this to split rows into logical groups, such as regions, categories, or years. Each group can then be styled on its own.
- HTML Code
- Browser Output
<table>
<caption>Sales by region (units)</caption>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Units</th>
</tr>
</thead>
<tbody>
<tr>
<th colspan="2" scope="colgroup">Europe</th>
</tr>
<tr><th scope="row">Notebook</th><td>420</td></tr>
<tr><th scope="row">Pen set</th><td>310</td></tr>
</tbody>
<tbody>
<tr>
<th colspan="2" scope="colgroup">Asia</th>
</tr>
<tr><th scope="row">Notebook</th><td>680</td></tr>
<tr><th scope="row">Pen set</th><td>540</td></tr>
</tbody>
</table>
| Product | Units |
|---|---|
| Europe | |
| Notebook | 420 |
| Pen set | 310 |
| Asia | |
| Notebook | 680 |
| Pen set | 540 |
Each group starts with a full-width heading cell (made with colspan, which you will learn in Colspan and Rowspan). The scope="colgroup" tells assistive technology that "Europe" labels the rows in its group.
rowgroup for the same ideaWhen a heading labels a group of rows, you will also see scope="rowgroup". Both express the same idea of "a heading for a section." Use the one your table's structure matches, and test with a screen reader if the table is complex.
<tfoot>: The Footβ
<tfoot> holds summary rows: totals, averages, counts, or notes that apply to the whole table.
<tfoot>
<tr>
<th scope="row">Total</th>
<td>1,190</td>
</tr>
</tfoot>
Good uses:
- A total at the bottom of a column of amounts
- An average row for scores
- A notes row, using
colspanto stretch across all columns
Do not put regular data in <tfoot>. It should only hold rows that summarize or explain the rows above.
Styling the Sectionsβ
Because the three sections are real elements, CSS can target each one.
thead th {
background: #0f172a;
color: #ffffff;
}
tbody tr:nth-child(even) {
background: #f8fafc; /* zebra stripes */
}
tbody tr:hover {
background: #e0f2fe;
}
tfoot th,
tfoot td {
font-weight: 700;
border-top: 2px solid #0f172a;
}
| Goal | Selector |
|---|---|
| Style the header | thead th |
| Stripe the data rows | tbody tr:nth-child(even) |
| Highlight a row on hover | tbody tr:hover |
| Emphasize totals | tfoot td, tfoot th |
| Add space between groups | tbody + tbody { border-top: 8px solid transparent; } |
Notice that tbody tr:nth-child(even) stripes only data rows, never the header. Without <tbody>, you would have to work around the header row in your CSS.
Sticky Headers for Long Tablesβ
For a table with many rows, keeping the header visible while you scroll is a big help. <thead> makes this simple.
.table-wrap {
max-height: 20rem;
overflow: auto;
}
thead th {
position: sticky;
top: 0;
background: #e2e8f0; /* needs a solid background */
}
<div class="table-wrap">
<table>
<thead>...</thead>
<tbody>... many rows ...</tbody>
</table>
</div>
The header cells stay at the top of the scrolling area while the body rows move beneath them. Give the cells a solid background, or the text underneath will show through.
Why Sections Matterβ
- Accessibility. Assistive technology can tell headers from body from footer, and some tools let users jump between them.
- Printing. Browsers can repeat
<thead>(and sometimes<tfoot>) on each printed page. - Styling. Cleaner CSS, with no tricks to skip the header row.
- Scripting. JavaScript sorting or filtering can safely target
tbodywithout touching the header or totals. - Readability. Anyone reading your code can see the structure at a glance.
When Can You Skip Them?β
For a tiny, simple table with a single header row, <thead> and <tbody> are optional, and the browser fills in a <tbody> automatically. But for any table that you will style, sort, print, or show to a screen reader user, use the sections. A good rule: if the table has a header row, use <thead> and <tbody>. Add <tfoot> only when you have a summary row.
Putting It Together: An Invoiceβ
- HTML Code
- Browser Output
<table>
<caption>Invoice 1042 (USD)</caption>
<thead>
<tr>
<th scope="col">Description</th>
<th scope="col">Qty</th>
<th scope="col">Price</th>
<th scope="col">Line total</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Notebook</th>
<td>3</td>
<td>3.50</td>
<td>10.50</td>
</tr>
<tr>
<th scope="row">Pen set</th>
<td>2</td>
<td>16.25</td>
<td>32.50</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row" colspan="3">Total</th>
<td>43.00</td>
</tr>
</tfoot>
</table>
| Description | Qty | Price | Line total |
|---|---|---|---|
| Notebook | 3 | 3.50 | 10.50 |
| Pen set | 2 | 16.25 | 32.50 |
| Total | 43.00 | ||
The three parts are clear: labels on top, line items in the middle, and a total at the bottom that spans the first three columns.
Global Audience Notesβ
- State the currency in the caption or header ("USD," "EUR," "INR") so totals are unambiguous.
- Number formats differ (1,190.00 or 1.190,00). Be consistent, and set the page
lang. - Label totals plainly ("Total," "Average"). Short words translate well.
- For right-to-left pages, the order of columns flips, but the order of
thead,tbody, andtfootstays the same.
Common Mistakes to Avoidβ
- Using the wrong order, such as
<tbody>before<thead>. - Mixing loose
<tr>rows with section elements. Put every row inside a section. - Using more than one
<thead>or<tfoot>. Only<tbody>can repeat. - Putting data rows in
<tfoot>. It is for summaries only. - Putting header rows in
<tbody>instead of<thead>. - Leaving out
<thead>on a table with a header row, which makes styling and accessibility harder. - Forgetting a solid background on sticky headers. The body text shows through.
- Striping rows with
tr:nth-child(even)and accidentally coloring the header. Usetbody tr. - Putting a caption or colgroup after the sections. They come first.
- Closing tags missing.
</thead>,</tbody>, and</tfoot>are good habits, even if browsers can sometimes guess.
Practice Timeβ
Try these in your editor:
- Take a table you built earlier, and wrap its header row in
<thead>. - Wrap the data rows in
<tbody>. - Add a
<tfoot>with a "Total" row. - Style
thead thwith a dark background and white text. - Add zebra stripes using
tbody tr:nth-child(even). - Highlight data rows on hover with
tbody tr:hover. - Split the data into two
<tbody>groups with a group heading each. - Make the footer text bold with a thick top border.
- Put a long table inside a scrolling
div, and keep the header visible withposition: sticky. - Bonus: print the page (or use print preview) and see how the header behaves across pages.
Quick Recapβ
Click a question to check your answer.
1. What are <thead>, <tbody>, and <tfoot> for?
They group the rows of a table by role: <thead> for the header rows, <tbody> for the main data, and <tfoot> for summary rows such as totals.
2. In what order do the sections go?
<caption> and <colgroup> first, then <thead>, then one or more <tbody>, then <tfoot>.
3. How many of each can a table have?
At most one <thead> and one <tfoot>, but one or more <tbody> sections.
4. Why would you use several <tbody> groups?
To split the data into logical groups, such as regions or categories, so each group can be labeled and styled separately.
5. How do you keep a table header visible while scrolling?
Put the table in a scrolling container, and apply position: sticky; top: 0; with a solid background to thead th.
6. What belongs in <tfoot>?
Summary rows such as totals, averages, or notes that apply to the whole table. Not regular data.
Key Takeawaysβ
<thead>,<tbody>, and<tfoot>give the rows of a table meaning: labels, data, and summary.- The order is
<thead>, then one or more<tbody>, then<tfoot>, after any<caption>and<colgroup>. - Use several
<tbody>groups for logical sections, each with its own group heading. - Sections make styling, sticky headers, printing, sorting, and accessibility much easier.
- If a table has a header row, use
<thead>and<tbody>. Add<tfoot>only for real summary rows.
You have seen colspan a few times in this lesson, in group headings and in the total row. It is time to learn how it works. Continue with Colspan and Rowspan, or return to the Tables overview.