Skip to content

Table Sections: <thead>, <tbody>, and <tfoot>

A basic table works fine for a few rows. Once a table grows — or needs to communicate clearly to screen readers and developers — you divide it into sections using <thead>, <tbody>, and <tfoot>.

ElementPurpose
<thead>Groups the header row(s) — the labels
<tbody>Groups the main data rows
<tfoot>Groups a summary or footer row at the bottom

These elements wrap <tr> rows. The hierarchy becomes: <table><thead> / <tbody> / <tfoot><tr><td> / <th>.

<table>
<tr>
<th>Rating</th>
<th>Typical Distance</th>
</tr>
<tr>
<td>Easy</td>
<td>Under 5 miles</td>
</tr>
<tr>
<td>Moderate</td>
<td>5–10 miles</td>
</tr>
</table>

This works, but there is no explicit separation between the header row and the data rows.

<table>
<thead>
<tr>
<th>Rating</th>
<th>Typical Distance</th>
</tr>
</thead>
<tbody>
<tr>
<td>Easy</td>
<td>Under 5 miles</td>
</tr>
<tr>
<td>Moderate</td>
<td>5–10 miles</td>
</tr>
<tr>
<td>Strenuous</td>
<td>Over 10 miles</td>
</tr>
</tbody>
</table>

The structure is now explicit. Any developer reading the HTML — or any screen reader scanning it — immediately understands which rows are headers and which are data.

<tfoot> holds a summary, total, or note row at the bottom of the table. It is optional but useful when the last row has a different meaning than the data rows above it.

<table>
<thead>
<tr>
<th>Trail</th>
<th>Distance (miles)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Pine Ridge Loop</td>
<td>4.2</td>
</tr>
<tr>
<td>Summit Approach</td>
<td>8.7</td>
</tr>
<tr>
<td>Valley Floor Trail</td>
<td>2.1</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Total</td>
<td>15.0</td>
</tr>
</tfoot>
</table>

The total row is semantically different from the data rows — <tfoot> makes that distinction explicit.

Adding <thead>, <tbody>, and <tfoot> does not change the visual appearance by default. The table looks identical with or without them. What they do change:

  • Readability — the HTML communicates intent more clearly
  • Accessibility — screen readers use section context to describe rows
  • CSS targeting — you can style thead, tbody, and tfoot separately without class names
  • Browser behavior — some browsers allow tbody to scroll independently while thead and tfoot remain fixed (relevant once you learn CSS)

Open index.html in VS Code.

Find the <table> you built in Lesson 01. You are going to wrap its rows in <thead> and <tbody> sections.

  1. Identify your header row (the one with <th> cells).
  2. Wrap that row in <thead></thead>.
  3. Wrap all the data rows in <tbody></tbody>.
  4. If your table has a summary or total row at the bottom, wrap that in <tfoot></tfoot>.

Before:

<table>
<tr>
<th>Rating</th>
<th>Typical Distance</th>
</tr>
<tr>
<td>Easy</td>
<td>Under 5 miles</td>
</tr>
</table>

After:

<table>
<thead>
<tr>
<th>Rating</th>
<th>Typical Distance</th>
</tr>
</thead>
<tbody>
<tr>
<td>Easy</td>
<td>Under 5 miles</td>
</tr>
</tbody>
</table>
  1. Save and reload in your browser. The page will look identical — but the structure is now complete.
  • <thead> groups header rows. <tbody> groups data rows. <tfoot> groups summary or footer rows.
  • These sections wrap <tr> elements — they sit between <table> and <tr>.
  • They do not change default visual appearance, but they improve structure, accessibility, and CSS targeting.
  • Write <tbody> explicitly even though browsers insert it automatically.