Can we have multiple <tbody>
tags in same <table>
? If yes then in what scenarios should we use multiple <tbody>
tags?
Can we have multiple in same ?
Asked
Active
Viewed 2.1e+01k times
642
TylerH
- 20,799
- 66
- 75
- 101
Jitendra Vyas
- 148,487
- 229
- 573
- 852
-
In addition, if you run a HTML document with multiple `` tags through [W3C's HTML Validator](http://validator.w3.org/check), with a HTML5 DOCTYPE, it will successfully validate.
– Bern
Aug 15 '12 at 08:18
7 Answers
774
Yes you can use them, for example I use them to more easily style groups of data, like this:
thead th { width: 100px; border-bottom: solid 1px #ddd; font-weight: bold; }
tbody:nth-child(odd) { background: #f5f5f5; border: solid 1px #ddd; }
tbody:nth-child(even) { background: #e5e5e5; border: solid 1px #ddd; }
<table>
<thead>
<tr><th>Customer</th><th>Order</th><th>Month</th></tr>
</thead>
<tbody>
<tr><td>Customer 1</td><td>#1</td><td>January</td></tr>
<tr><td>Customer 1</td><td>#2</td><td>April</td></tr>
<tr><td>Customer 1</td><td>#3</td><td>March</td></tr>
</tbody>
<tbody>
<tr><td>Customer 2</td><td>#1</td><td>January</td></tr>
<tr><td>Customer 2</td><td>#2</td><td>April</td></tr>
<tr><td>Customer 2</td><td>#3</td><td>March</td></tr>
</tbody>
<tbody>
<tr><td>Customer 3</td><td>#1</td><td>January</td></tr>
<tr><td>Customer 3</td><td>#2</td><td>April</td></tr>
<tr><td>Customer 3</td><td>#3</td><td>March</td></tr>
</tbody>
</table>
You can view an example here. It'll only work in newer browsers, but that's what I'm supporting in my current application, you can use the grouping for JavaScript etc. The main thing is it's a convenient way to visually group the rows to make the data much more readable. There are other uses of course, but as far as applicable examples, this one is the most common one for me.
TylerH
- 20,799
- 66
- 75
- 101
Nick Craver
- 623,446
- 136
- 1,297
- 1,155
-
6
ok thanks for great answer. Does is matter to screen reader , one `tbody` or multiple?
– Jitendra Vyas
Jun 19 '10 at 18:49
-
1
@metal-gear-solid - In my experience they handle them fine, e.g.: as if they were one ``. When you start to *nest* tables, that's what usually gives real navigation problems for a screen reader.
– Nick Craver
Jun 19 '10 at 18:53
-
So use of `` is just for visual. there is no semantic difference between one `` and multiple ``
– Jitendra Vyas
Jun 20 '10 at 03:36
-
11
@metal: no, there is a semantic difference - multiple `` elements describes separate groups in the table, as was explained in the answer. Also I should add that it's generally better to target cells for backgrounds, so the CSS should be, for example, `tbody:nth-child(odd) td { background: #f5f5f5; }`
– DisgruntledGoat
Jun 21 '10 at 12:40
-
5
-
8
@TimDown - when I said "newer browsers" it was only referring to the CSS `:nth-child()` usage for the linked demonstration, the multiple `` will work in any browser.
– Nick Craver
Dec 17 '12 at 14:10
-
1
This is a pretty straightforward idea, but what to do when you need some kind of hierarchy, 3-7 levels deep? You're not supposed to embed TBODY elements into each other, right?
– Laszlo T
Jan 25 '17 at 14:31
-
-
Logically and semantically, having multiple TBODY elements in a TABLE does not make sense.
– user1451111
Jul 21 '17 at 11:38
-
I used this approach in an attempt to keep groups of rows together when printing the page, using `tbody { page-break-inside: avoid; }`. Note however that this doen not work in Chrome. The reason is explained in [Page break not working with tbody issue](https://stackoverflow.com/questions/27753255/page-break-not-working-with-tbody-issue) and has to do with a _tbody_ not being a block element, by default.
– R. Schreurs
Mar 28 '18 at 10:58
310
Yes. From the DTD
<!ELEMENT table
(caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
So it expects one or more. It then goes on to say
Use multiple tbody sections when rules
are needed between groups of table
rows.
Martin Smith
- 438,706
- 87
- 741
- 845
-
13
As of the HTML5 spec, this changes slightly, but the fundamental "yes, multiple `tbody` elements are fine) remains. Specifically, you're now [allowed to put the one `tfoot` element *after* the `tbody` if you like](http://www.w3.org/TR/html5/tabular-data.html#the-table-element). (They neatly side-stepped the DTD aspect [by saying they don't provide one](http://www.w3.org/TR/html5/the-xhtml-syntax.html#the-xhtml-syntax).) :-)
– T.J. Crowder
Jul 11 '13 at 17:51
49
According to this example from the spec it can be done: w3-struct-tables.
Table rows may be grouped into a table head, table foot, and one or more table body sections, using the THEAD, TFOOT and TBODY elements, respectively.
TylerH
- 20,799
- 66
- 75
- 101
Kris van der Mast
- 16,343
- 8
- 39
- 61
17
Martin Joiner's problem is caused by a misunderstanding of the <caption>
tag.
The <caption>
tag defines a table caption.
The <caption>
tag must be the first child of the <table>
tag.
You can specify only one caption per table.
Also, note that the scope
attribute should be placed on a <th>
element and not on a <tr>
element.
The proper way to write a multi-header multi-tbody table would be something like this :
<table id="dinner_table">
<caption>This is the only correct place to put a caption.</caption>
<tbody>
<tr class="header">
<th colspan="2" scope="col">First Half of Table (British Dinner)</th>
</tr>
<tr>
<th scope="row">1</th>
<td>Fish</td>
</tr>
<tr>
<th scope="row">2</th>
<td>Chips</td>
</tr>
<tr>
<th scope="row">3</th>
<td>Peas</td>
</tr>
<tr>
<th scope="row">4</th>
<td>Gravy</td>
</tr>
</tbody>
<tbody>
<tr class="header">
<th colspan="2" scope="col">Second Half of Table (Italian Dinner)</th>
</tr>
<tr>
<th scope="row">5</th>
<td>Pizza</td>
</tr>
<tr>
<th scope="row">6</th>
<td>Salad</td>
</tr>
<tr>
<th scope="row">7</th>
<td>Oil</td>
</tr>
<tr>
<th scope="row">8</th>
<td>Bread</td>
</tr>
</tbody>
</table>
John Slegers
- 45,213
- 22
- 199
- 169
-
Spec recommends using `scope="rowgroup"` (instead of `col`) for the `tbody` headers. See [Example](https://html.spec.whatwg.org/multipage/tables.html#the-th-element).
– CletusW
Feb 12 '20 at 20:22
8
Yes. I use them for dynamically hiding/revealing the relevant part of a table, e.g. a course.
Viz.
<table>
<tbody id="day1" style="display:none">
<tr><td>session1</td><tr>
<tr><td>session2</td><tr>
</tbody>
<tbody id="day2">
<tr><td>session3</td><tr>
<tr><td>session4</td><tr>
</tbody>
<tbody id="day3" style="display:none">
<tr><td>session5</td><tr>
<tr><td>session6</td><tr>
</tbody>
</table>
A button can be provided to toggle between everything or just the current day by manipulating tbodies without processing many rows individually.
CPslashM
- 81
- 1
- 1
3
EDIT: The caption
tag belongs to table and thus should only exist once. Do not associate a caption
with each tbody
element like I did:
<table>
<caption>First Half of Table (British Dinner)</caption>
<tbody>
<tr><th>1</th><td>Fish</td></tr>
<tr><th>2</th><td>Chips</td></tr>
<tr><th>3</th><td>Pease</td></tr>
<tr><th>4</th><td>Gravy</td></tr>
</tbody>
<caption>Second Half of Table (Italian Dinner)</caption>
<tbody>
<tr><th>5</th><td>Pizza</td></tr>
<tr><th>6</th><td>Salad</td></tr>
<tr><th>7</th><td>Oil</td></tr>
<tr><th>8</th><td>Bread</td></tr>
</tbody>
</table>
BAD EXAMPLE ABOVE: DO NOT COPY
The above example does not render as you would expect because writing like this indicates a misunderstanding of the caption
tag. You would need lots of CSS hacks to make it render correctly because you would be going against standards.
I searched for W3Cs standards on the caption
tag but could not find an explicit rule that states there must be only one caption
element per table but that is in fact the case.
Martin Joiner
- 3,529
- 2
- 23
- 49
2
I have created a JSFiddle where I have two nested ng-repeats with tables, and the parent ng-repeat on tbody. If you inspect any row in the table, you will see there are six tbody elements, i.e. the parent level.
HTML
<div>
<table class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th>Store ID</th>
<th>Name</th>
<th>Address</th>
<th>City</th>
<th>Cost</th>
<th>Sales</th>
<th>Revenue</th>
<th>Employees</th>
<th>Employees H-sum</th>
</tr>
</thead>
<tbody data-ng-repeat="storedata in storeDataModel.storedata">
<tr id="storedata.store.storeId" class="clickableRow" title="Click to toggle collapse/expand day summaries for this store." data-ng-click="selectTableRow($index, storedata.store.storeId)">
<td>{{storedata.store.storeId}}</td>
<td>{{storedata.store.storeName}}</td>
<td>{{storedata.store.storeAddress}}</td>
<td>{{storedata.store.storeCity}}</td>
<td>{{storedata.data.costTotal}}</td>
<td>{{storedata.data.salesTotal}}</td>
<td>{{storedata.data.revenueTotal}}</td>
<td>{{storedata.data.averageEmployees}}</td>
<td>{{storedata.data.averageEmployeesHours}}</td>
</tr>
<tr data-ng-show="dayDataCollapse[$index]">
<td colspan="2"> </td>
<td colspan="7">
<div>
<div class="pull-right">
<table class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th></th>
<th>Date [YYYY-MM-dd]</th>
<th>Cost</th>
<th>Sales</th>
<th>Revenue</th>
<th>Employees</th>
<th>Employees H-sum</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="dayData in storeDataModel.storedata[$index].data.dayData">
<td class="pullright">
<button type="btn btn-small" title="Click to show transactions for this specific day..." data-ng-click=""><i class="icon-list"></i>
</button>
</td>
<td>{{dayData.date}}</td>
<td>{{dayData.cost}}</td>
<td>{{dayData.sales}}</td>
<td>{{dayData.revenue}}</td>
<td>{{dayData.employees}}</td>
<td>{{dayData.employeesHoursSum}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
(
Side note:
This fills up the DOM if you have a lot of data on both levels, so I am therefore working on a directive to fetch data and replace, i.e. adding into DOM when clicking parent and removing when another is clicked or same parent again.
To get the kind of behavior you find on Prisjakt.nu, if you scroll down to the computers listed and click on the row (not the links). If you do that and inspect elements you will see that a tr is added and then removed if parent is clicked again or another.
)
Pixic
- 1,345
- 1
- 17
- 29

- 20,799
- 66
- 75
- 101

- 148,487
- 229
- 573
- 852
-
In addition, if you run a HTML document with multiple `` tags through [W3C's HTML Validator](http://validator.w3.org/check), with a HTML5 DOCTYPE, it will successfully validate. – Bern Aug 15 '12 at 08:18
7 Answers
Yes you can use them, for example I use them to more easily style groups of data, like this:
thead th { width: 100px; border-bottom: solid 1px #ddd; font-weight: bold; }
tbody:nth-child(odd) { background: #f5f5f5; border: solid 1px #ddd; }
tbody:nth-child(even) { background: #e5e5e5; border: solid 1px #ddd; }
<table>
<thead>
<tr><th>Customer</th><th>Order</th><th>Month</th></tr>
</thead>
<tbody>
<tr><td>Customer 1</td><td>#1</td><td>January</td></tr>
<tr><td>Customer 1</td><td>#2</td><td>April</td></tr>
<tr><td>Customer 1</td><td>#3</td><td>March</td></tr>
</tbody>
<tbody>
<tr><td>Customer 2</td><td>#1</td><td>January</td></tr>
<tr><td>Customer 2</td><td>#2</td><td>April</td></tr>
<tr><td>Customer 2</td><td>#3</td><td>March</td></tr>
</tbody>
<tbody>
<tr><td>Customer 3</td><td>#1</td><td>January</td></tr>
<tr><td>Customer 3</td><td>#2</td><td>April</td></tr>
<tr><td>Customer 3</td><td>#3</td><td>March</td></tr>
</tbody>
</table>
You can view an example here. It'll only work in newer browsers, but that's what I'm supporting in my current application, you can use the grouping for JavaScript etc. The main thing is it's a convenient way to visually group the rows to make the data much more readable. There are other uses of course, but as far as applicable examples, this one is the most common one for me.

- 20,799
- 66
- 75
- 101

- 623,446
- 136
- 1,297
- 1,155
-
6ok thanks for great answer. Does is matter to screen reader , one `tbody` or multiple? – Jitendra Vyas Jun 19 '10 at 18:49
-
1@metal-gear-solid - In my experience they handle them fine, e.g.: as if they were one ``. When you start to *nest* tables, that's what usually gives real navigation problems for a screen reader. – Nick Craver Jun 19 '10 at 18:53
-
So use of `` is just for visual. there is no semantic difference between one `` and multiple `` – Jitendra Vyas Jun 20 '10 at 03:36
-
11@metal: no, there is a semantic difference - multiple `` elements describes separate groups in the table, as was explained in the answer. Also I should add that it's generally better to target cells for backgrounds, so the CSS should be, for example, `tbody:nth-child(odd) td { background: #f5f5f5; }` – DisgruntledGoat Jun 21 '10 at 12:40
-
5
-
8@TimDown - when I said "newer browsers" it was only referring to the CSS `:nth-child()` usage for the linked demonstration, the multiple `` will work in any browser. – Nick Craver Dec 17 '12 at 14:10
-
1This is a pretty straightforward idea, but what to do when you need some kind of hierarchy, 3-7 levels deep? You're not supposed to embed TBODY elements into each other, right? – Laszlo T Jan 25 '17 at 14:31
-
-
Logically and semantically, having multiple TBODY elements in a TABLE does not make sense. – user1451111 Jul 21 '17 at 11:38
-
I used this approach in an attempt to keep groups of rows together when printing the page, using `tbody { page-break-inside: avoid; }`. Note however that this doen not work in Chrome. The reason is explained in [Page break not working with tbody issue](https://stackoverflow.com/questions/27753255/page-break-not-working-with-tbody-issue) and has to do with a _tbody_ not being a block element, by default. – R. Schreurs Mar 28 '18 at 10:58
Yes. From the DTD
<!ELEMENT table
(caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
So it expects one or more. It then goes on to say
Use multiple tbody sections when rules are needed between groups of table rows.

- 438,706
- 87
- 741
- 845
-
13As of the HTML5 spec, this changes slightly, but the fundamental "yes, multiple `tbody` elements are fine) remains. Specifically, you're now [allowed to put the one `tfoot` element *after* the `tbody` if you like](http://www.w3.org/TR/html5/tabular-data.html#the-table-element). (They neatly side-stepped the DTD aspect [by saying they don't provide one](http://www.w3.org/TR/html5/the-xhtml-syntax.html#the-xhtml-syntax).) :-) – T.J. Crowder Jul 11 '13 at 17:51
According to this example from the spec it can be done: w3-struct-tables.
Table rows may be grouped into a table head, table foot, and one or more table body sections, using the THEAD, TFOOT and TBODY elements, respectively.

- 20,799
- 66
- 75
- 101

- 16,343
- 8
- 39
- 61
Martin Joiner's problem is caused by a misunderstanding of the <caption>
tag.
The <caption>
tag defines a table caption.
The <caption>
tag must be the first child of the <table>
tag.
You can specify only one caption per table.
Also, note that the scope
attribute should be placed on a <th>
element and not on a <tr>
element.
The proper way to write a multi-header multi-tbody table would be something like this :
<table id="dinner_table">
<caption>This is the only correct place to put a caption.</caption>
<tbody>
<tr class="header">
<th colspan="2" scope="col">First Half of Table (British Dinner)</th>
</tr>
<tr>
<th scope="row">1</th>
<td>Fish</td>
</tr>
<tr>
<th scope="row">2</th>
<td>Chips</td>
</tr>
<tr>
<th scope="row">3</th>
<td>Peas</td>
</tr>
<tr>
<th scope="row">4</th>
<td>Gravy</td>
</tr>
</tbody>
<tbody>
<tr class="header">
<th colspan="2" scope="col">Second Half of Table (Italian Dinner)</th>
</tr>
<tr>
<th scope="row">5</th>
<td>Pizza</td>
</tr>
<tr>
<th scope="row">6</th>
<td>Salad</td>
</tr>
<tr>
<th scope="row">7</th>
<td>Oil</td>
</tr>
<tr>
<th scope="row">8</th>
<td>Bread</td>
</tr>
</tbody>
</table>

- 45,213
- 22
- 199
- 169
-
Spec recommends using `scope="rowgroup"` (instead of `col`) for the `tbody` headers. See [Example](https://html.spec.whatwg.org/multipage/tables.html#the-th-element). – CletusW Feb 12 '20 at 20:22
Yes. I use them for dynamically hiding/revealing the relevant part of a table, e.g. a course. Viz.
<table>
<tbody id="day1" style="display:none">
<tr><td>session1</td><tr>
<tr><td>session2</td><tr>
</tbody>
<tbody id="day2">
<tr><td>session3</td><tr>
<tr><td>session4</td><tr>
</tbody>
<tbody id="day3" style="display:none">
<tr><td>session5</td><tr>
<tr><td>session6</td><tr>
</tbody>
</table>
A button can be provided to toggle between everything or just the current day by manipulating tbodies without processing many rows individually.

- 81
- 1
- 1
EDIT: The caption
tag belongs to table and thus should only exist once. Do not associate a caption
with each tbody
element like I did:
<table>
<caption>First Half of Table (British Dinner)</caption>
<tbody>
<tr><th>1</th><td>Fish</td></tr>
<tr><th>2</th><td>Chips</td></tr>
<tr><th>3</th><td>Pease</td></tr>
<tr><th>4</th><td>Gravy</td></tr>
</tbody>
<caption>Second Half of Table (Italian Dinner)</caption>
<tbody>
<tr><th>5</th><td>Pizza</td></tr>
<tr><th>6</th><td>Salad</td></tr>
<tr><th>7</th><td>Oil</td></tr>
<tr><th>8</th><td>Bread</td></tr>
</tbody>
</table>
BAD EXAMPLE ABOVE: DO NOT COPY
The above example does not render as you would expect because writing like this indicates a misunderstanding of the caption
tag. You would need lots of CSS hacks to make it render correctly because you would be going against standards.
I searched for W3Cs standards on the caption
tag but could not find an explicit rule that states there must be only one caption
element per table but that is in fact the case.

- 3,529
- 2
- 23
- 49
I have created a JSFiddle where I have two nested ng-repeats with tables, and the parent ng-repeat on tbody. If you inspect any row in the table, you will see there are six tbody elements, i.e. the parent level.
HTML
<div>
<table class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th>Store ID</th>
<th>Name</th>
<th>Address</th>
<th>City</th>
<th>Cost</th>
<th>Sales</th>
<th>Revenue</th>
<th>Employees</th>
<th>Employees H-sum</th>
</tr>
</thead>
<tbody data-ng-repeat="storedata in storeDataModel.storedata">
<tr id="storedata.store.storeId" class="clickableRow" title="Click to toggle collapse/expand day summaries for this store." data-ng-click="selectTableRow($index, storedata.store.storeId)">
<td>{{storedata.store.storeId}}</td>
<td>{{storedata.store.storeName}}</td>
<td>{{storedata.store.storeAddress}}</td>
<td>{{storedata.store.storeCity}}</td>
<td>{{storedata.data.costTotal}}</td>
<td>{{storedata.data.salesTotal}}</td>
<td>{{storedata.data.revenueTotal}}</td>
<td>{{storedata.data.averageEmployees}}</td>
<td>{{storedata.data.averageEmployeesHours}}</td>
</tr>
<tr data-ng-show="dayDataCollapse[$index]">
<td colspan="2"> </td>
<td colspan="7">
<div>
<div class="pull-right">
<table class="table table-hover table-condensed table-striped">
<thead>
<tr>
<th></th>
<th>Date [YYYY-MM-dd]</th>
<th>Cost</th>
<th>Sales</th>
<th>Revenue</th>
<th>Employees</th>
<th>Employees H-sum</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="dayData in storeDataModel.storedata[$index].data.dayData">
<td class="pullright">
<button type="btn btn-small" title="Click to show transactions for this specific day..." data-ng-click=""><i class="icon-list"></i>
</button>
</td>
<td>{{dayData.date}}</td>
<td>{{dayData.cost}}</td>
<td>{{dayData.sales}}</td>
<td>{{dayData.revenue}}</td>
<td>{{dayData.employees}}</td>
<td>{{dayData.employeesHoursSum}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
( Side note: This fills up the DOM if you have a lot of data on both levels, so I am therefore working on a directive to fetch data and replace, i.e. adding into DOM when clicking parent and removing when another is clicked or same parent again. To get the kind of behavior you find on Prisjakt.nu, if you scroll down to the computers listed and click on the row (not the links). If you do that and inspect elements you will see that a tr is added and then removed if parent is clicked again or another. )

- 1,345
- 1
- 17
- 29