About This HTML Issue
When you write a <table> without explicitly using <thead> and <tbody>, the HTML parser automatically wraps your <tr> elements in an implicit <tbody>. If any of those rows contain <th> elements intended as column headers, the validator flags them because <th> cells in <tbody> are unexpected — the parser sees header cells appearing in what should be the data body of the table.
While <th> elements are technically valid inside <tbody> (for example, as row headers), this warning usually indicates a structural problem: your column headers aren’t properly separated from your data rows. Properly structuring your table with <thead> and <tbody> matters for several reasons:
-
Accessibility: Screen readers use table structure to help users navigate. A
<thead>section clearly identifies column headers, making it easier for assistive technology to announce what each data cell represents. -
Styling and behavior: CSS selectors like
thead thandtbody tdlet you target headers and data cells independently. Browsers can also use<thead>and<tbody>to enable scrollable table bodies while keeping headers fixed. - Standards compliance: Explicitly defining table sections removes ambiguity and ensures consistent parsing across all browsers.
To fix this issue, wrap the row containing your <th> column headers in a <thead> element, and wrap your data rows in a <tbody> element.
Examples
❌ Incorrect: <th> in implicit table body
Here, the parser wraps all rows in an implicit <tbody>, so the <th> elements end up inside the table body:
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<tr>
<td>Liza</td>
<td>49</td>
</tr>
<tr>
<td>Joe</td>
<td>47</td>
</tr>
</table>
✅ Correct: <th> in explicit <thead>
Wrapping the header row in <thead> and data rows in <tbody> resolves the issue:
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr>
<td>Liza</td>
<td>49</td>
</tr>
<tr>
<td>Joe</td>
<td>47</td>
</tr>
</tbody>
</table>
✅ Correct: <th> as row headers inside <tbody>
If you intentionally use <th> elements inside <tbody> as row headers, add the scope attribute to clarify their purpose. This is valid and won’t trigger the warning when the table also has a proper <thead>:
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Age</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Liza</th>
<td>49</td>
</tr>
<tr>
<th scope="row">Joe</th>
<td>47</td>
</tr>
</tbody>
</table>
The scope="row" attribute tells assistive technology that these <th> cells are headers for their respective rows, while scope="col" identifies column headers. This combination provides the best accessibility for table data.
Find issues like this automatically
Rocket Validator scans thousands of pages in seconds, detecting HTML issues across your entire site.