The <tbody> Element
Technical Summary
tbody is a child of table that groups the table's body rows. The HTML Standard treats tbody, thead, and tfoot as row groups. A row-group boundary is part of the table model and can affect cell-header relationships.
When the HTML parser encounters a tr directly inside table, it inserts an implicit tbody. The HTMLTableSectionElement interface exposes rows, insertRow(), and deleteRow() for working with rows in a section.
Definition, context, and content model
| Item | Specification | Conditions and scope |
|---|---|---|
| Meaning | A row group for the table body | Groups body rows separately from heading and footer sections |
| Context | A child of table | Subject to the parent content model and ordering rules for caption, colgroup, thead, table body, and tfoot |
| Content model | Zero or more tr elements and script-supporting elements | An empty tbody is permitted by the content model |
| Start and end tags | May be omitted under specific HTML syntax conditions | The start tag can be omitted when the first content is a tr and it is not immediately preceded by a tbody, thead, or tfoot whose end tag was omitted. The end tag can be omitted before another tbody or a tfoot, or when the parent has no more content. |
| DOM interface | HTMLTableSectionElement | thead and tfoot use the same interface |
| Element-specific attributes | None | Global attributes are allowed |
The table content model allows the body to be represented by zero or more tbody elements, or by one or more tr elements. When a tr token appears directly inside a table insertion mode, however, the HTML parser inserts a tbody in the DOM. Source conformance and the parser's DOM construction and recovery rules are separate.
Row groups and the table model
A row group is a unit of rows in the table grid. A cell cannot occupy slots in more than one row group. Therefore, rowspan="0" spans the remaining rows in the cell's own row group and does not continue into the next tbody.
| Element | Typical purpose | Table-model category |
|---|---|---|
thead | Contains the column-heading section | Row group |
tbody | Contains the table's body rows | Row group; may occur more than once |
tfoot | Contains footer rows such as totals | Row group |
Do not infer a DOM row group from visual styling alone. Check the actual parent element and table model. The distinction also matters to the scope of th scope="rowgroup" and to the structure exposed to assistive technology.
DOM built by the parser
<table id="items">
<tr><td>Apples</td></tr>
</table>
// DOM built by the HTML parser
table#items
└─ tbody (inserted implicitly)
└─ tr
└─ td
When tr appears in the “in table” insertion mode, the parser inserts a tbody with no attributes and reprocesses the token. In the “in table body” insertion mode, a td or th appearing directly is a parse error; the parser inserts a tr with no attributes and reprocesses the cell token. These are parser-recovery behaviors, not authoring patterns to rely on.
HTMLTableSectionElement APIs
| Member | Return value or behavior | Range and boundary |
|---|---|---|
tbody.rows | An HTMLCollection of the section's direct tr children | A live collection that reflects row insertion and removal |
table.tBodies | An HTMLCollection of the table's child tbody elements | Does not include thead, tfoot, or tbody elements in a nested table |
tbody.insertRow(index) | Creates a tr and inserts it into the section | -1 appends; an out-of-range index throws IndexSizeError |
tbody.deleteRow(index) | Removes the selected tr from the section | -1 removes the last row; empty-section and index boundaries follow the method's index rules |
const table = document.querySelector("table");
const body = table.tBodies[0];
body.rows.length; // rows in this tbody
body.insertRow(-1); // append a row to this tbody
body.deleteRow(-1); // remove its last row
tbody.rows returns rows in that section. table.rows is a collection for the whole table. The names are similar, but their scopes differ.
Accessibility mapping
HTML-AAM maps native tbody to the WAI-ARIA rowgroup role. This exposes a structural grouping within a table; it is not a visible heading string. Record the browser, operating system, and table structure when observing the browser Accessibility tree or a platform API.
Facts and evidence
Specification claims and implementation measurements are recorded separately. A browser role query is not itself the normative HTML-AAM mapping or a platform Accessibility API measurement.
| Type | Fact or claim | Conditions and scope | Status | Evidence |
|---|---|---|---|---|
| SPEC | tbody is a child of table; its content model allows zero or more tr and script-supporting elements. | Includes table-child ordering, tag omission, and global attributes. | Specification checked | HTML Standard: the tbody element |
| SPEC | A cell cannot span slots from multiple row groups; rowspan="0" covers the remaining rows in the same row group. | Table grid, row-group boundary, and cell span. | Specification checked | HTML Standard: forming a table |
| SPEC | The parser inserts an implicit tbody before a tr token in a table. A direct td/th in a table body triggers an implicit tr and token reprocessing. | Distinguishes parser recovery from conforming authoring markup. | Specification checked | HTML Standard: in table / in table body |
| SPEC | HTML-AAM maps tbody to the rowgroup role. | Distinguishes the normative mapping from individual browser Accessibility trees and platform API observations. | Specification checked | HTML-AAM: tbody |
Evidence
- HTML Standard: The
tbodyelement — context, content model, tag omission, and section APIs - HTML Standard: Forming a table — row groups, cell slots, and row-span boundaries
- HTML Standard: Table parsing modes — implied section and row insertion
- HTML Accessibility API Mappings:
tbody—rowgrouprole and platform mappings - Web Platform Tests:
the-tbody-element— section row collection and row methods - Web Platform Tests:
rowspan-0.html— row-span behavior at a row-group boundary
Implementation Evidence
Specification requirements are recorded separately from browser observations, WPT runs, and Accessibility role queries. Each result applies only to its stated fixture, browser, and test selection.
| Type | Checked behavior | Conditions | Status |
|---|---|---|---|
| IMPL | HTMLTableSectionElement, tbody.rows / table.tBodies, insertRow() / deleteRow(), createTBody(), parser-inserted tbody / tr, and the rowspan=0 row-group boundary |
2026-10-17 JST / Chrome UA 155.0.0.0 (Windows NT 10.0 UA) / viewport 1280×720 / navigator.language=ja. Served build-config/yugien/research-results/tbody-v1/ at loopback 127.0.0.1:4197; fixture checks passed 13/13. Confirmed the section interface and live row collection, the scope of table.tBodies, parser-inserted sections and rows, row insertion/deletion and boundary errors, empty-section deleteRow(-1), createTBody(), and the rowspan group boundary. One Chrome environment only; other browsers, OS build, and platform APIs were not measured. |
Partial observation (Chrome 155 / fixture 13/13) |
| WPT | Selected WPTs for tbody.rows, insertRow() / deleteRow(), and the rowspan=0 row-group boundary |
2026-10-17 JST / Chrome UA 155.0.0.0 / wpt.live. rows.html: 1/1 passed; insertRow.html: 6/6 passed; deleteRow.html: 6/6 passed; rowspan-0.html: 2/2 passed. Total: 15/15 passed. The exact upstream revision served by wpt.live could not be pinned. Unselected tests and other browsers were not run.The test version used was not recorded. The linked test may change, so this result cannot be repeated with certainty against the same version. |
Selected tests only (wpt.live revision not pinned) |
| AAM | Native table, row group, row, column header, row header, and data cell exposure through Chrome browser role queries | 2026-10-17 JST / Chrome UA 155.0.0.0 (Windows NT 10.0 UA) / viewport 1280×720 / navigator.language=ja. Queried the fixture table named Stock by group and observed 1 table, 4 row groups (thead, two tbody sections, and tfoot), 6 rows, 2 column headers, 5 row headers, and 5 cells. This is a browser role-query observation; Windows UI Automation and screen-reader speech were not measured. |
Partial observation (Chrome 155 browser role query / one environment) |
A browser role/name query is not the same measurement as a platform Accessibility API or screen-reader speech. The tested layer and unmeasured scope are reported separately.
Coverage and open issues
- Specification checkedContent model, table ordering, row groups, table parser, HTMLTableSectionElement, and HTML-AAM mapping
- OpenImplementation evidence covers one selected fixture and browser environment; parser recovery branches and varied table structures remain untested
- OpenWPT evidence covers selected files. The exact upstream revision served by wpt.live and other browsers were not confirmed
- OpenPlatform Accessibility APIs, Windows UI Automation, assistive-technology speech, other operating systems, and Expert-gap Review
This Topic has initial coverage. Passing selected checks does not show that every HTML structure, browser, operating system, or assistive technology behaves the same way.
Related surfaces
For a beginner-friendly example, see How to Use the tbody Element in Yugien. The table Topic covers the table model; the header row group is covered in the thead Topic; summary row groups are covered in the tfoot Topic; tr covers rows and indexes; and th and td cover headers and data cells.