Command Palette

Search for a command to run...

Hectal
PHASE 3Beginner ~19 min· topic 2 of 4

Topic 3.2

Table Structure

In one line

A well-built table has a <caption> that names it, row groups (<thead>, <tbody>, <tfoot>) that separate headers, data and totals, and optional <colgroup>/<col> elements that style whole columns.

Think of it like this

A shop receipt. At the top it says what it is ("Receipt, Green Grocers, 12 March"): that's the caption. Then a line of column titles (Item, Qty, Price): the head. Then the items you bought: the body. At the bottom, the total: the foot. You'd never mix the total in among the items, and a table shouldn't either.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

caption
The visible title of a table, written as the first thing inside it.
Accessible name
The name a screen reader announces for an element. For a table, the caption gives it one.
Row group
A set of rows treated together: the head, a body, or the foot of a table.
thead
The row group with the header rows.
tbody
A row group with data rows. A table can have several.
tfoot
The row group with summary rows, like totals. Always drawn at the bottom.
colgroup / col
Elements that stand for columns, used to set widths and backgrounds for a whole column at once.
span (on col)
How many columns one <col> or <colgroup> stands for.

Step by step

01Name the table

Start with a caption, the first thing inside <table>. It says what the data is about, in a few words.

receipt.htmlwhole filehtml
<table>
  <caption>Receipt: Green Grocers, 12 March</caption>
</table>

02Put the header row in thead

The row of column titles goes into <thead>. Its cells are <th>.

receipt.htmlwhole filehtml
<table>
  <caption>Receipt: Green Grocers, 12 March</caption>
  <thead>
    <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
  </thead>
</table>

03Put the data in tbody

Each item becomes a row in <tbody>. The item name labels its row, so it is a <th>.

receipt.htmlwhole filehtml
  <tbody>
    <tr><th>Rice, 5 kg</th><td>1</td><td>₹350</td></tr>
    <tr><th>Milk, 1 L</th><td>2</td><td>₹130</td></tr>
  </tbody>

04Put the total in tfoot

The total row goes last, in <tfoot>. Its label spans the first two columns, using colspan (explained fully in Topic 3.3).

receipt.htmlwhole filehtml
  <tfoot>
    <tr><th colspan="2">Total</th><td>₹480</td></tr>
  </tfoot>

05Describe the columns

Add a <colgroup> right after the caption. Here the first column gets a width and the price column a soft background. col elements are empty: they have no closing tag and hold no content.

receipt.htmlwhole filehtml
  <colgroup>
    <col style="width: 12em">
    <col>
    <col class="money">
  </colgroup>
Describe the columnsdiagram
Rendering diagram…

Try it yourself

  1. 1

    Move the caption

    In the receipt example, add caption { caption-side: bottom; } to the CSS. Predict: does the caption move in the HTML, or only on screen? Inspect it to check.

  2. 2

    Add a section

    In the rainfall example, add a third <tbody> with October (90 mm). Predict the new values of Body sections, Total rows and Footer row index before you run.

  3. 3

    Find the column rule

    In the last example, move color: red from .highlight to a new rule td:nth-child(2) { color: red; }. Now the numbers turn red: styling the cells works where styling the column didn't.

Code & diagrams

A receipt with every part New tab

JavaScript can reach each part directly: `caption`, `tHead`, `tBodies` and `tFoot`. The green column comes from the `col` element.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Caption: Receipt: Green Grocers, 12 March
Header rows: 1
Body rows: 3
Footer total: ₹540
Several tbody sections and a footer written first New tab

The footer is written before the bodies (old HTML 4 style), yet it is drawn last and its `rowIndex` is the last one. Write it last anyway: that's today's rule.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Body sections: 2
Total rows: 6
Footer row index: 5
What a col can and can't style New tab

The second column turns yellow and wider, but its text stays black and left-aligned. Only `background`, `width`, `border` and `visibility` work on columns.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Put the caption outside the table

In the receipt example, move <caption>…</caption> to just above <table>, and run.

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Cannot read properties of null (reading 'textContent')

Break #2

Style text through a column

Add <col style="text-align: right"> for the price column and expect the prices to line up right.

terminal
$ the page shows
── what you'll see ──
The prices stay left-aligned. No error appears anywhere.

Myth vs fact

Myth

A <caption> is the same as a heading above the table.

Fact

A heading isn't tied to the table. A caption belongs to it: screen readers announce it as the table's name, and it moves with the table.

Myth

A table can only have one <tbody>.

Fact

It can have as many as you need. Several bodies split the data into sections, each of which CSS and JavaScript can treat separately.

Myth

tfoot must go before tbody.

Fact

That was HTML 4. The current standard puts tfoot last; browsers draw it at the bottom either way.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    table.rows lists thead rows first, then body rows in source order, then tfoot rows, which is why the footer's rowIndex is always last. DOM helpers like table.createTHead(), createTFoot(), createCaption() and insertRow() put new parts in the correct position for you.

  • ▸

    When printing, Chromium and Firefox repeat the thead at the top of each page of a long table (support for repeating tfoot is less consistent). Keeping headers in thead is what makes this possible.

  • ▸

    Columns can be hidden with visibility: collapse on a col: the column disappears and the table shrinks, without changing the HTML. For a static table, plain CSS on the cells (td:nth-child(n)) is often simpler and better supported.

  • ▸

    Use a figure with a figcaption around a table only if you need the figure semantics (for example a numbered "Table 3" referred to from text); otherwise caption is the right, simpler name for the table.

Remember this

  1. 1

    <caption> gives the table a visible title. It must be the first child of <table>. Screen readers use it as the table's accessible name (the name announced when someone reaches the table, like "table, Exam marks 2026, 4 columns, 5 rows"), so it helps people pick the right table on a page with several. Style its position with CSS caption-side: bottom if you want it below.

  2. 2

    Rows are grouped into row groups. <thead> holds the header row or rows. <tbody> holds the data rows; a table may have several <tbody> elements to split data into sections (for example one per month). <tfoot> holds summary rows such as totals. A table has at most one thead and one tfoot.

  3. 3

    The order inside <table> is fixed by the HTML standard: caption first, then any colgroups, then thead, then the tbody elements (or plain rows), then tfoot last. Older HTML 4 allowed tfoot before tbody, and browsers still draw such a footer at the bottom, but today's rule is to write it last.

  4. 4

    Row groups are useful beyond meaning. CSS can target them (thead th, tbody tr:nth-child(even) for stripes), JavaScript gets them directly (table.tHead, table.tBodies, table.tFoot), and when a long table is printed, browsers can repeat the thead at the top of every page.

  5. 5

    <colgroup> and <col> describe columns, which otherwise exist only through the cells. They come right after the caption. <col span="2"> stands for two columns. Their main use is styling: you can set width, background, border (with border-collapse: collapse) and visibility: collapse on a column. Other properties, like color or text-align, are not applied to the cells through col; style the cells for those.

  6. 6

    Since <tbody> is added for you when missing, the question is never "does my table have a tbody?" (it always does once parsed) but "did I put the header rows in a thead and the totals in a tfoot?" That's the part only you can do.

Explain it without notes

01

What does each of caption, thead, tbody and tfoot do, and in what order are they written?

02

Why can't you right-align text in a column with <col style="text-align: right">?

03

When would you use more than one <tbody>?

Practice

01

Build a small expense table for a trip with a caption, a thead (Item, Cost), three items in tbody and a total in tfoot.

02

Give the Cost column a light background using colgroup/col, and right-align the costs.

Trade-offs

  • ↔

    col styling is short (one element styles a whole column) but limited to four properties; cell selectors like td:nth-child(3) can do anything but must be kept in sync if columns move.

  • ↔

    Several tbody sections add structure and styling hooks, at the cost of a little more markup; for a short flat list of rows, one body is enough.

Done when you can

  • I can give a table a caption in the right place.

  • I can split rows into thead, one or more tbody, and tfoot, in the correct order.

  • I can use colgroup and col with span to size and colour columns.

  • I know which CSS properties work on columns and which don't.

  • I can reach a table's parts from JavaScript with caption, tHead, tBodies and tFoot.