HTML Table Structure: table, tr, td, and the Grid Model

Published Updated

An HTML table is built from three nested elements, table wraps the whole thing, tr is one row, and td is one data cell inside that row.

Syntax

<table>
  <tr>
    <td>cell</td>
    <td>cell</td>
  </tr>
</table>

The table element is the outer container. Every direct child of it is a row written as tr, which stands for table row. Inside each tr you place one td per cell, where td stands for table data. That is the entire skeleton, and everything else you learn about tables hangs off these three.

Columns Build Themselves

There is no separate element you write for a column. You never write a col tag to place data, and you never count columns by hand. Columns appear because every row lists its cells in the same left-to-right order, so the first td of every row stacks into column one, the second td into column two, and so on down the table.

<table>
  <tr>
    <td>Ada Lovelace</td>
    <td>Analyst</td>
  </tr>
  <tr>
    <td>Alan Turing</td>
    <td>Cryptographer</td>
  </tr>
</table>

The names line up in the left column and the roles in the right column because each row put them in that order. The grid is an emergent thing here, you describe rows and the columns fall out.

Keep the Cell Count Even

Because columns come from position, a row with the wrong number of cells throws the whole grid off. If most rows have three cells and one row has two, that short row leaves a ragged gap where the missing cell would sit. The browser will not warn you, it just renders the hole.

<table>
  <tr>
    <td>Espresso</td>
    <td>Hot</td>
    <td>3.00</td>
  </tr>
  <tr>
    <td>Cold brew</td>
    <td>4.50</td>
  </tr>
</table>

That second row only has two cells, so the table renders unevenly and the prices no longer line up under one column. Count your cells per row, and when a cell genuinely needs to cover more than one slot, reach for the spanning attributes instead of leaving a row short.

When to Reach for a Table

A table earns its place when the content genuinely has rows and columns that carry meaning, the way a price list or a weekly schedule does, where each row is one record and each column is one property of that record. Reaching for table just to line up a few boxes on a page is a common beginner move, but that job belongs to CSS, usually flexbox or grid, which handles visual alignment without dragging tabular semantics into a layout problem. A useful test: if you could drop the content into a spreadsheet without losing anything, it belongs in a table. If the columns only exist to keep things looking neat, reach for CSS layout instead of a table you do not actually need.

The Accessibility Gap Before Headers

This table renders fine today with plain td cells, and a sighted reader tracks the columns visually because the browser lines every column up in neat vertical bands. A screen reader has no equivalent shortcut. It reads the markup in source order, cell by cell, with no sense that column two of row three relates to column two of row one, because nothing in a plain td-only table says which cell is a label and which is data. That gap is exactly what the next page's th, scope, and caption close, so if you are building a table anyone might navigate by screen reader, treat headers as part of the structure rather than an optional polish step.

Example

<table>
  <tr>
    <td>Monday</td>
    <td>Closed</td>
  </tr>
  <tr>
    <td>Tuesday</td>
    <td>9 to 5</td>
  </tr>
  <tr>
    <td>Wednesday</td>
    <td>9 to 5</td>
  </tr>
</table>

This renders as three rows of two cells. The days form the left column and the hours form the right column, with each row contributing one cell to each. There is no header yet, so a screen reader reads it as a plain grid of data, which is the next thing you will want to fix.

Frequently Asked Questions

Do you need a tr around table cells?

Yes. Cells live inside rows, and a td outside a tr is invalid. Browsers usually repair it, but the repaired structure may not be the one you intended, which is a slow way to find the mistake.

What are colgroup and col actually for?

They let you target whole columns for a small set of properties, chiefly width, background, and border. They do not create the columns, which come from the cells, and they cannot carry per-cell content or headers.

Can you nest a table inside another table?

It is valid, and it is almost always the wrong answer. Nested tables make the relationships hard to announce and hard to make responsive. The exception is HTML email, where table layout is still the practical option.

Why does an empty cell collapse or look wrong?

Because a cell with no content has nothing to give it height, and older border models hide empty cells entirely. Put the placeholder in the cell rather than leaving it blank, so the row keeps its shape and the meaning stays explicit.

Can a cell cover more than one row or column without colspan and rowspan?

No. Those attributes are the only way to make one cell occupy several grid slots. CSS can make a cell look wider, but the table model still counts it as one slot, so the header relationships and the cell count per row stay unchanged.

Sources

  1. [1]
    The Table element
    (developer.mozilla.org)
  2. [2]
  3. [3]