657

Can we have multiple <tbody> tags in same <table>? If yes then in what scenarios should we use multiple <tbody> tags?

1
  • In addition, if you run a HTML document with multiple <tbody> tags through W3C's HTML Validator, with a HTML5 DOCTYPE, it will successfully validate.
    – Bern
    Commented Aug 15, 2012 at 8:18

7 Answers 7

785

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.

10
  • 6
    ok thanks for great answer. Does is matter to screen reader , one tbody or multiple? Commented Jun 19, 2010 at 18:49
  • 1
    @metal-gear-solid - In my experience they handle them fine, e.g.: as if they were one <tbody>. When you start to nest tables, that's what usually gives real navigation problems for a screen reader. Commented Jun 19, 2010 at 18:53
  • 11
    @metal: no, there is a semantic difference - multiple <tbody> 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; } Commented Jun 21, 2010 at 12:40
  • 5
    What is the definition of "newer browsers"?
    – Tim Down
    Commented Dec 17, 2012 at 11:07
  • 8
    @TimDown - when I said "newer browsers" it was only referring to the CSS :nth-child() usage for the linked demonstration, the multiple <tbody> will work in any browser. Commented Dec 17, 2012 at 14:10
312

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.

1
52

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.

0
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>

1
  • Spec recommends using scope="rowgroup" (instead of col) for the tbody headers. See Example.
    – CletusW
    Commented Feb 12, 2020 at 20:22
10

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.

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">&nbsp;</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. )

2

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.