HTMLBeginner

HTML Tables In Depth: thead, tbody, rowspan, colspan

Learn HTML tables: table, tr, th, td, thead/tbody/tfoot, merging cells with rowspan and colspan, captions, accessible headers, and when to avoid tables.

All HTML lessons

What you will learn

A table is for one specific job: showing data that has rows and columns, like a price list, a timetable, or a comparison of products. In this lesson you will learn the elements that build a table, how to group it into a header, body and footer, how to merge cells with rowspan and colspan, how to make a table properly accessible, and — just as important — when a table is the wrong choice.

The building blocks

Every table is built from four elements working together:

<table>
  <tr>
    <th>Name</th>
    <th>Score</th>
  </tr>
  <tr>
    <td>Aman</td>
    <td>92</td>
  </tr>
</table>
  • <table> — wraps the entire table.
  • <tr> (table row) — one horizontal row. Everything in a table lives inside a <tr>.
  • <th> (table header) — a header cell. Bold and centred by default, and it tells browsers, screen readers and search engines “this cell is a label for a row or column”, not actual data.
  • <td> (table data) — an ordinary data cell.

Read that example row by row: the first <tr> holds two <th> labels (“Name”, “Score”), and the second <tr> holds the matching <td> data (“Aman”, “92”).

Grouping a table: <thead>, <tbody>, <tfoot>

Real tables are usually organised into three sections:

<table>
  <thead>
    <tr>
      <th>Item</th>
      <th>Quantity</th>
      <th>Price</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Apple</td>
      <td>5</td>
      <td>Rs 120</td>
    </tr>
    <tr>
      <td>Banana</td>
      <td>10</td>
      <td>Rs 58</td>
    </tr>
  </tbody>

  <tfoot>
    <tr>
      <td colspan="2"><strong>Total</strong></td>
      <td><strong>Rs 178</strong></td>
    </tr>
  </tfoot>
</table>
  • <thead> groups the header row(s) — the column labels.
  • <tbody> groups the actual data rows. A table can technically have more than one <tbody>, to group related sets of rows.
  • <tfoot> groups a footer row — commonly a total or summary.

Why bother with these three, instead of just a pile of <tr> elements? Two real benefits:

  1. Meaning. A screen reader can announce “table header” versus “table body” versus “table footer”, so a user understands the table’s structure, not just its cells.
  2. Long tables can repeat the header. When you print a long table, or scroll a very tall one, browsers can keep the <thead> visible or reprint it on every page — something a table without <thead> cannot do.

<tbody> is technically optional (browsers insert it invisibly if you leave it out), but write it explicitly. It documents your intent and makes the table easier to style with CSS.

Merging cells: colspan and rowspan

Sometimes one cell needs to span more than one column or row — a category label that covers several items, for example.

colspan: merge across columns

<table>
  <tr>
    <td colspan="2"><strong>Total</strong></td>
    <td><strong>Rs 178</strong></td>
  </tr>
</table>

colspan="2" makes that one cell take up the width of two columns. This is exactly how the total row of a table usually looks: one wide label cell, then the total value.

rowspan: merge down rows

<table>
  <tr>
    <th rowspan="2">Category</th>
    <th colspan="2">Details</th>
    <th rowspan="2">Price</th>
  </tr>
  <tr>
    <th>Item</th>
    <th>Quantity</th>
  </tr>
  <tr>
    <td rowspan="2">Fruits</td>
    <td>Apple</td>
    <td>5</td>
    <td>Rs 120</td>
  </tr>
  <tr>
    <td>Banana</td>
    <td>10</td>
    <td>Rs 58</td>
  </tr>
  <tr>
    <td>Vegetables</td>
    <td>Carrot</td>
    <td>7</td>
    <td>Rs 15</td>
  </tr>
</table>

Walk through this carefully, because it is the part beginners find confusing:

  • The header’s “Category” and “Price” cells use rowspan="2", so each one stretches down and covers both header rows. That’s why the second header row only needs two more <th> cells (“Item”, “Quantity”) — “Category” and “Price” are already filling that space from above.
  • In the body, “Fruits” uses rowspan="2", covering both the Apple row and the Banana row. Because that cell already “used up” a slot in the Banana row, the Banana <tr> has one fewer <td> than a normal row — it only lists Banana’s own item, quantity and price.

The golden rule of rowspan/colspan: every time a cell spans extra rows or columns, the rows or columns “underneath” it must have one fewer cell, because that cell is already covered. Getting this count wrong is the single most common table mistake, and it visibly breaks the grid.

Giving the table a title: <caption>

<table>
  <caption>Weekly Fruit Order</caption>
  <thead>
    ...
  </thead>
  ...
</table>

<caption> is the table’s title, and it must be the first element right after the opening <table> tag. Unlike a heading placed above the table, a <caption> is programmatically linked to the table — a screen reader announces it as “Weekly Fruit Order, table” before reading the data, so the user always knows what they’re listening to.

Making tables accessible with scope

Add scope to a <th> to say exactly what it is a header for — a column or a row. This is a small addition with a big accessibility payoff.

<table>
  <tr>
    <th scope="col">Name</th>
    <th scope="col">Score</th>
  </tr>
  <tr>
    <th scope="row">Aman</th>
    <td>92</td>
  </tr>
  <tr>
    <th scope="row">Priya</th>
    <td>88</td>
  </tr>
</table>
  • scope="col" — this header describes the column below it.
  • scope="row" — this header describes the row next to it.

With scope in place, a screen reader user who lands on a data cell far down a long table can ask “which row and column am I in?” and get a correct answer, instead of having to count cells manually. Get in the habit of adding scope="col" to every header row — it costs nothing and helps real users.

Styling a table with CSS

The border-collapse property is worth knowing immediately, because a plain table looks broken without it — every cell gets its own separate double border.

table {
  width: 100%;
  border-collapse: collapse;
}

th, td {
  border: 1px solid #999;
  padding: 8px;
  text-align: left;
}

thead {
  background: #f0f0f0;
}

tbody tr:nth-child(even) {
  background: #fafafa;
}

border-collapse: collapse merges neighbouring borders into one line instead of doubled lines. tbody tr:nth-child(even) is the CSS trick behind the classic “striped rows” look — you will learn selectors like this properly in the CSS course.

When NOT to use a table

Before HTML had proper layout tools, developers misused tables to arrange whole page layouts — a sidebar in one <td>, the main content in another. Never do this. A table announces “this is a grid of data” to screen readers; using one for layout makes a screen reader read out meaningless “row 1, column 1” navigation for what is actually a page header or a sidebar.

Use a table only for real tabular data: prices, schedules, comparisons, statistics, scoreboards. For page layout — sidebars, cards side by side, headers and footers — use <div> (or a semantic element) with CSS Flexbox or Grid instead, which you’ll learn in the CSS course.

Common mistakes

  • Miscounting cells after using rowspan/colspan. Remember: a row “covered” by a spanning cell from above needs fewer <td>/<th> cells. Count carefully.
  • Skipping <thead>/<tbody>. The table still renders, but you lose the structural meaning and some browser features like header-repeating on print.
  • Forgetting scope on header cells. The table looks fine visually but is much harder to navigate with a screen reader.
  • Putting <caption> anywhere other than right after <table>. It must be the table’s first child to work correctly.
  • Using a table for page layout. Use CSS Flexbox/Grid instead; reserve tables for genuine tabular data.
  • Nesting a block element like <div> directly inside <tr>, outside a <td>/<th>. A <tr> may only contain header/data cells.
  • Forgetting border-collapse: collapse, and ending up with a table full of doubled, ugly borders.

Interview-style questions

What is the difference between <th> and <td>? <th> is a header cell — it labels a row or column and carries semantic meaning for accessibility and search engines. <td> is an ordinary data cell with no special meaning.

What do rowspan and colspan do? rowspan makes a cell span multiple rows vertically; colspan makes a cell span multiple columns horizontally. Any row or column “underneath” a spanning cell needs one fewer cell to keep the grid aligned.

Why use <thead>, <tbody> and <tfoot> instead of just <tr> elements? They give the table proper semantic structure for screen readers, let browsers repeat the header when printing or scrolling a long table, and make the table easier to style with CSS.

When should you avoid using a table? For arranging general page layout (sidebars, headers, columns of unrelated content) — that misuse breaks accessibility. Tables should only hold real tabular data.

Practice

  1. Use the Practice in Editor button. Add a third product row to the table body, and update the <tfoot> total to match.

  2. Add scope="col" to each <th> in the header row if it is missing, then try adding a second table with scope="row" headers down the left side, like the Aman/Priya example.

  3. Style the table with border-collapse: collapse and give <thead> a light background colour.

  4. Fix this code — it has a rowspan cell count mismatch (the second row has one cell too many):

    <table>
      <tr>
        <th rowspan="2">Category</th>
        <th>Item</th>
      </tr>
      <tr>
        <td>Fruits</td>
        <td>Apple</td>
      </tr>
    </table>

Recap

  • A table is built from <table>, <tr> (rows), <th> (header cells) and <td> (data cells).
  • Group a table into <thead>, <tbody> and <tfoot> for meaning, accessibility, and easier styling.
  • colspan merges cells across columns, rowspan merges cells down rows — and any row/column they cover needs one fewer cell.
  • <caption> gives the table a title and must come right after the opening <table> tag.
  • Add scope="col" or scope="row" to header cells so screen readers can navigate the table correctly.
  • Use border-collapse: collapse to avoid doubled borders, and never use a table for page layout — that’s a job for CSS Flexbox or Grid.