HTML table rowspan and colspan

colspan stretches a cell sideways, rowspan stretches it downwards, and each one means deleting the cells it swallowed. Getting the arithmetic wrong is what produces the ragged table.

In an HTML table, rowspan and colspan merge cells: colspan stretches a cell across columns, rowspan stretches it down through rows.

<tr>
  <th colspan="2">Q1 total</th>
  <td>1,204</td>
</tr>

The number is the total count of cells covered, including the cell itself. colspan="2" means this cell plus one more, so one <td> has to be removed from that row.

A table with a header cell spanning two columns above a pair of subheadings.
A table with a header cell spanning two columns above a pair of subheadings.

The arithmetic that keeps the table square

Every table row has to account for the same number of columns. A span does not add space, it consumes cells, and those cells must be deleted from the markup.

  • colspan="n" removes n minus 1 cells from the same row.
  • rowspan="n" removes one cell from each of the n minus 1 rows below it, in the same column position.
  • Both together removes the cells in the whole rectangle, minus the one you kept.

Getting this wrong is the single cause of the ragged table. The browser does not report an error. It draws the extra cells anyway, and one row ends up a column wider than the rest.

<!-- wrong: four columns declared, five cells worth of content -->
<tr><td colspan="2">Subtotal</td><td>A</td><td>B</td><td>C</td></tr>

<!-- right -->
<tr><td colspan="2">Subtotal</td><td>A</td><td>B</td></tr>

Counting rowspan, which is harder

colspan is easy because the deletions are next to the span. rowspan puts them in later rows, which is where people lose track.

<tr>
  <th rowspan="3">North</th>
  <td>January</td><td>412</td>
</tr>
<tr>
  <td>February</td><td>388</td>
</tr>
<tr>
  <td>March</td><td>501</td>
</tr>

Rows two and three have two cells each, not three. The first column is already occupied by the spanning cell above them.

When you add a fourth month later, the rowspan has to become 4. Nothing reminds you, and a stale span leaves a hole at the bottom of the group.

What spans cost

Feature Effect of spans
table-layout: fixed widths Spanned cells do not follow the colgroup cleanly
Sorting by a column Breaks, because rows are no longer independent
Responsive stacking Breaks, since a spanning cell has no single row
Screen reader header pairing Positional matching fails without extra attributes
Copy into a spreadsheet Merged cells arrive merged, or shift the columns

That list is the argument for using spans in headers and avoiding them in the body. A header block is read once. A data body is sorted, filtered, stacked and pasted elsewhere.

If you need sorting, see sort and filter, and keep the body free of spans.

Making merged headers readable to a screen reader

A plain table lets assistive technology match a cell to its header by position. Spans break that assumption.

For a two level header, scope is usually enough:

<tr>
  <th rowspan="2" scope="col">Client</th>
  <th colspan="2" scope="colgroup">2026</th>
</tr>
<tr>
  <th scope="col">Q1</th>
  <th scope="col">Q2</th>
</tr>

For anything more tangled, use explicit ids:

<th id="h-client" scope="col">Client</th>
<td headers="h-client">Aldridge</td>

The headers attribute is verbose and it is the only thing that works on a genuinely complex table. Semantic HTML covers the wider habit of letting the markup carry the meaning.

A two level header where one cell spans two quarters and the row below names each quarter.
A two level header where one cell spans two quarters and the row below names each quarter.

Borders and merged cells

Spans and border-collapse interact. Under collapse, a spanning cell shares edges with several neighbours, and the conflict resolution can drop a rule you expected on one side.

Under separate with zero spacing, each cell carries its own border, and the spanning cell keeps a clean outline. That is usually the better choice for a table with merged headers. Border collapse explains the resolution order.

When not to merge

Three cases where the merge is hiding a different problem.

  1. Repeating a label down a group. A rowspan here is tidy on screen and awful for reuse. If the table will be sorted or exported, repeat the label in every row instead.
  2. A title above the table. Use <caption>, not a full width spanning row. It is shorter and it is announced as the table title.
  3. Layout. Spans used to position things on a page belong to an era before grid. CSS grid does that job.
The same grouped data twice: on the left with a rowspan label, on the right with the label repeated in every row.
The same grouped data twice: on the left with a rowspan label, on the right with the label repeated in every row.

Checking the result

Look at the table in a browser and count the columns in the widest row against the narrowest. If they differ, a span is wrong. The rendered table is a faster check than reading the markup.

Open it in the HTML file opener to see it without your project's stylesheet, where a padding rule cannot disguise a missing cell. To edit values without touching the structure, the editable HTML table tool types straight into the cells.

Handing the table over

A merged header table is often the final form of a report, which means the next step is getting it in front of someone.

Paste the HTML into a NOS document. It renders as written, spans and all, at an address of its own. Share, then Share link, then Create link produces an unlisted link that opens in one click.

Correct a figure later by clicking the text in the document. The address does not change, so the link you sent already points at the corrected version, and there is no second copy circulating.

Questions people ask

What is the difference between rowspan and colspan?

colspan makes one cell occupy several columns in the same row, merging across. rowspan makes one cell occupy several rows in the same column, merging down. Both take a number, which is the total count of cells the merged cell covers, including itself.

Why is my table ragged after adding a colspan?

You added the span but did not remove the cells it now covers. A cell with colspan="3" replaces three cells, so two of the original cells in that row must be deleted. The same applies to rowspan, except the deletions are in the rows below.

Can a cell have both rowspan and colspan?

Yes. A cell with rowspan="2" and colspan="2" covers a two by two block, so three other cells disappear: one in the same row and two in the row below. This is normal in header blocks and rare in body data.

Do merged cells cause accessibility problems?

They can. A screen reader pairs each data cell with its headers by position, and spans break that positional logic. Add scope="colgroup" or scope="rowgroup" to spanning header cells, and for complex layouts give each data cell a headers attribute listing the ids of its header cells.

Keep reading