HTML Guides for tbody
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The HTML specification requires that every row in a table has at least one cell that starts on that row. A “cell beginning on a row” means a <td> or <th> element that is directly placed in that <tr>, as opposed to a cell that merely spans into it via rowspan from an earlier row. When the validator encounters a row with no cells beginning on it, it flags the error because the row is structurally meaningless — it contributes nothing to the table’s data model.
This issue commonly arises in two scenarios:
- Empty <tr> elements — A <tr> that contains no <td> or <th> children at all. This sometimes appears when developers use empty rows for visual spacing, or when content management systems generate leftover markup.
- Rows fully covered by rowspan — When cells in preceding rows use rowspan values large enough to span over an entire subsequent row, that subsequent row ends up with no cells beginning on it, even if it technically “has” cells passing through it.
This matters for several reasons. Screen readers and other assistive technologies rely on a well-formed table structure to navigate cells and announce their content. An empty or fully-spanned row confuses this navigation. Browsers may also handle malformed tables inconsistently, leading to unexpected rendering. Ensuring every row has at least one cell that begins on it keeps your tables semantically correct and accessible.
How to fix it
- Remove empty rows. If a <tr> has no cells and serves no purpose, delete it entirely.
- Add cells to the row. If the row is intentional, populate it with <td> or <th> elements (they can be empty if needed).
- Adjust rowspan values. If previous cells span too many rows, reduce their rowspan so that every row still has at least one cell of its own.
- Use CSS for spacing. If empty rows were used for visual spacing, use CSS margin, padding, or border-spacing instead.
Note that self-closing <tr /> elements are treated the same as <tr></tr> — they produce an empty row and will trigger this error.
Examples
Empty row in <tbody> (incorrect)
<table>
<tbody>
<tr>
</tr>
<tr>
<td>Data</td>
</tr>
</tbody>
</table>
The first <tr> has no cells, so the validator reports that row 1 of the <tbody> row group has no cells beginning on it.
Empty row removed (correct)
<table>
<tbody>
<tr>
<td>Data</td>
</tr>
</tbody>
</table>
Row fully covered by rowspan (incorrect)
<table>
<tbody>
<tr>
<td rowspan="3">Spans three rows</td>
<td rowspan="3">Also spans three</td>
</tr>
<tr>
</tr>
<tr>
</tr>
</tbody>
</table>
Rows 2 and 3 have no cells beginning on them — all cells originate from row 1. Even though cells pass through those rows via rowspan, the validator still requires at least one cell to begin on each row.
Corrected rowspan with cells on every row (correct)
<table>
<tbody>
<tr>
<td rowspan="3">Spans three rows</td>
<td>Row 1 detail</td>
</tr>
<tr>
<td>Row 2 detail</td>
</tr>
<tr>
<td>Row 3 detail</td>
</tr>
</tbody>
</table>
Each row now has at least one <td> that begins on it, satisfying the requirement.
Using CSS instead of empty rows for spacing (correct)
<table>
<tbody>
<tr>
<td>First item</td>
</tr>
<tr style="height: 1.5em;">
<td>Second item with extra space above</td>
</tr>
</tbody>
</table>
Instead of inserting an empty row for spacing, apply CSS to the row or cells that need additional space.
When you use the rowspan attribute on a table cell, you’re telling the browser that cell should vertically span across multiple rows. However, each row group element — <thead>, <tbody>, and <tfoot> — acts as a boundary. A cell’s rowspan cannot extend beyond the row group that contains it. If a <tbody> has 2 rows and a cell in the first row declares rowspan="4", the cell tries to span into rows that don’t exist within that group. The validator reports that the cell span is “clipped to the end of the row group.”
This matters for several reasons. First, assistive technologies like screen readers rely on accurate table structure to navigate cells and announce row/column relationships. A rowspan that overshoots its row group creates a mismatch between the declared structure and the actual rendered table, which can confuse users. Second, browsers handle this inconsistently — most will silently clip the span, but the rendered result may not match your intent. Third, if you later add a separate <tbody> or <tfoot> after the current group, the clipped span won’t bridge into it, potentially breaking your layout in unexpected ways.
To fix this, you have two options: reduce the rowspan value to match the number of remaining rows in the row group, or add enough rows to the group so the span fits. You should also check whether your row group boundaries (<thead>, <tbody>, <tfoot>) are placed where you actually intend them.
Note that this same issue applies to colspan exceeding the column count of a row group, though the warning message specifically mentions rowspan clipping to the end of the row group established by a <tbody> (or <thead> / <tfoot>) element.
Examples
Incorrect: rowspan exceeds available rows in <tbody>
This <tbody> has only 2 rows, but the first cell declares rowspan="4":
<table>
<tbody>
<tr>
<td rowspan="4">Spans too far</td>
<td>Row 1</td>
</tr>
<tr>
<td>Row 2</td>
</tr>
</tbody>
</table>
The cell tries to span 4 rows, but only 2 exist in the <tbody>. The browser clips it to 2 rows, and the validator reports the error.
Fixed: Reduce rowspan to match available rows
<table>
<tbody>
<tr>
<td rowspan="2">Spans two rows</td>
<td>Row 1</td>
</tr>
<tr>
<td>Row 2</td>
</tr>
</tbody>
</table>
Fixed: Add rows to accommodate the span
If you actually need the cell to span 4 rows, add the missing rows:
<table>
<tbody>
<tr>
<td rowspan="4">Spans four rows</td>
<td>Row 1</td>
</tr>
<tr>
<td>Row 2</td>
</tr>
<tr>
<td>Row 3</td>
</tr>
<tr>
<td>Row 4</td>
</tr>
</tbody>
</table>
Incorrect: rowspan crosses a row group boundary
This is a common source of the error — a span in one <tbody> trying to reach into the next:
<table>
<tbody>
<tr>
<td rowspan="3">Tries to cross groups</td>
<td>Group 1, Row 1</td>
</tr>
<tr>
<td>Group 1, Row 2</td>
</tr>
</tbody>
<tbody>
<tr>
<td>Group 2, Row 1</td>
</tr>
</tbody>
</table>
The cell with rowspan="3" cannot span from the first <tbody> (2 rows) into the second <tbody>. Each group is independent.
Fixed: Merge the row groups or adjust the span
If the rows logically belong together, combine them into a single <tbody>:
<table>
<tbody>
<tr>
<td rowspan="3">Spans all three rows</td>
<td>Row 1</td>
</tr>
<tr>
<td>Row 2</td>
</tr>
<tr>
<td>Row 3</td>
</tr>
</tbody>
</table>
Ready to validate your sites?
Start your free trial today.