What do <thead>, <tbody> and <tfoot> add to an HTML table, and where does <tfoot> go in the current HTML standard?
answer
- rows come in meaningful groups
- printing behaves differently per group
- one thead, one tfoot, many tbody
- the tfoot rule changed after HTML 4.01
- rowgroup scope needs a real group
basics
~20 sThey group a table's rows into header, body and footer sections, which lets browsers repeat header rows across printed pages, lets assistive tech report row groups, and gives scope="rowgroup" something to refer to. In current HTML, <tfoot> comes last.
solid answer
~50 sThey are the table's row groups. `<thead>` holds the header rows, `<tbody>` holds the data rows — and a table may have several `<tbody>` elements to split the data into meaningful sections — and `<tfoot>` holds summary rows such as totals. The grouping is not decoration: browsers repeat `<thead>` rows at the top of each page when a long table is printed or paginated, assistive technology can report which row group a cell is in, and `scope="rowgroup"` needs a row group to point at. Placement matters: in the current HTML standard `<tfoot>` is written **last**, after the `<tbody>`; HTML 4.01 required it before `<tbody>` so a browser could render the footer before the whole body had streamed in. Also worth knowing that if you write bare `<tr>`s, the parser inserts an implicit `<tbody>` in the DOM anyway.
go deeper
Know the three row groups and what kind of rows go in each, and that in current HTML <tfoot> is written last. Do not claim the groups change cells into headers.
Explain the concrete payoffs: repeated header rows when printing, row-group information in the accessibility tree, a target for scope="rowgroup", and the implicit <tbody> the parser inserts when you omit it.
Show that you use multiple <tbody> sections deliberately for meaningful groupings and label them properly, and that you can explain why the HTML 4.01 <tfoot>-first rule existed and why it no longer applies.
Own the structural convention for data-heavy products: where sectioning belongs, when a multi-section table should instead be several tables, and how printed and paginated output is verified rather than assumed.
## The three row groups `<thead>`, `<tbody>` and `<tfoot>` divide a table's rows into sections with meaning: - `<thead>` — the rows of column headers. - `<tbody>` — the data rows. A table may contain **several** `<tbody>` elements, which is the supported way to break a long table into labelled sections. - `<tfoot>` — summary rows: totals, averages, a count. Only one `<thead>` and one `<tfoot>` are permitted per table; `<tbody>` is the repeatable one. ```html <table> <caption>Order totals</caption> <thead> <tr> <th scope="col">Item</th> <th scope="col">Amount</th> </tr> </thead> <tbody> <tr><th scope="row">Widget</th><td>40.00</td></tr> <tr><th scope="row">Gadget</th><td>25.00</td></tr> </tbody> <tfoot> <tr><th scope="row">Total</th><td>65.00</td></tr> </tfoot> </table> ``` ## Where <tfoot> goes — and why the answer changed In the current HTML standard, `<tfoot>` comes **after** the table's body rows, at the end of the table. In HTML 4.01 it was the opposite: `<tfoot>` had to be written *before* `<tbody>`. The old rule existed for streaming — a browser receiving a very long table over a slow connection could render the footer immediately, before the body rows arrived, since it renders visually at the bottom regardless of source order. Modern HTML dropped that requirement, and the source order now matches the reading order. This is a favourite interview probe precisely because a lot of older tutorials still teach the HTML 4 order. If you learned "tfoot goes before tbody", you learned a rule that is now wrong. ## What the grouping actually buys **Printing and pagination.** The default rendering treats `<thead>` as a header row group, and browsers repeat those rows at the top of each printed page of a long table. Without `<thead>`, page two of a printed report is a wall of unlabelled numbers. `<tfoot>` gets analogous treatment as a footer group. **Row-group semantics for assistive technology.** The row groups appear in the accessibility tree, so a screen reader can tell the user which section of a table they are in, and can distinguish header rows from data rows without guessing which rows near the top look header-ish. **A target for scope="rowgroup".** A header cell that heads a whole section — the name of a `<tbody>` group, for instance — uses `scope="rowgroup"`, which is meaningless unless the rows are actually grouped. **Sane scripting and sane structure.** Sorting, filtering and virtualising a table are all operations on the body rows; having them in one or more `<tbody>` elements means "the data" is an addressable thing rather than "all the `<tr>`s except the first one". ## The implicit tbody If you write rows directly inside `<table>` with no `<tbody>`, the HTML parser inserts one for you: the resulting DOM has a `<tbody>` even though your source did not. That is why a selector or traversal written against the source structure can surprise you. Writing `<tbody>` yourself makes source and DOM agree. ## What the groups do not do They are not a substitute for header markup. Putting a row inside `<thead>` does not make its cells header cells — you still write `<th>` with the right `scope`. A `<thead>` full of `<td>` elements is a header row group with no headers in it. Equally, `<tfoot>` does not compute anything; a totals row is markup, and the total is a number you put there. ## Practical guidance Use all three when the table has genuine header, data and summary rows; use `<thead>` and `<tbody>` as the baseline for any data table. Split into multiple `<tbody>` elements only when the sections mean something to the reader, and label those sections with a header cell scoped to the row group rather than with a styled data row that reads like a heading but is not one.
- Can a table have more than one <tbody>, and why would you want that?Yes — `<thead>` and `<tfoot>` are limited to one each, but `<tbody>` repeats. Multiple bodies let you split a long table into meaningful sections, each introduced by a header cell with `scope="rowgroup"`, so assistive technology can report the section a cell belongs to instead of the reader inferring it from a visually styled divider row.
- Does putting a row inside <thead> make its cells header cells?No. `<thead>` groups rows; it does not change what the cells are. Cells inside it are still `<td>` unless you write `<th>`, and they still need `scope` to state which cells they head. A `<thead>` full of `<td>` elements is a header section containing no headers.
saying these in an interview costs you the question
- Says <tfoot> must be written before <tbody>
- Thinks <thead> alone makes cells into headers
- Believes <tfoot> calculates totals
- Assumes a table may contain only one <tbody>
- Treats row groups as purely a styling hook