HTML Spanning Cells: colspan and rowspan

Published Updated

When one cell needs to cover more than a single grid slot, colspan stretches it across columns and rowspan stretches it down across rows.

Syntax

<td colspan="2">covers two columns</td>
<td rowspan="2">covers two rows</td>

Both attributes take a whole number, the count of slots the cell should cover. A value of 2 means the cell takes its own slot plus the next one, 3 means three slots, and the default of 1 is just a normal cell. They work on td and th alike.

colspan Stretches Across Columns

Reach for colspan when one cell should sit over several columns, the classic case being a heading that titles a whole section of the table. The spanned cell eats slots from its own row, so a row where one cell covers two columns holds one fewer cell than a full row.

<table>
  <tr>
    <th colspan="2">Morning menu</th>
  </tr>
  <tr>
    <td>Espresso</td>
    <td>3.00</td>
  </tr>
</table>

The heading row has a single th because that one cell covers both columns. The data row below it still has two cells, so the table stays two columns wide and the heading stretches the full width.

rowspan Stretches Down Rows

rowspan does the same thing vertically, letting one cell run down through several rows. It is useful when a label applies to a group of rows, like a category that owns the two items beneath it. The cell starts in its own row and reaches down into the rows below, and each of those lower rows then writes one fewer cell because the spanned cell already fills their first slot.

<table>
  <tr>
    <th rowspan="2">Coffee</th>
    <td>Espresso</td>
  </tr>
  <tr>
    <td>Cold brew</td>
  </tr>
</table>

The "Coffee" header spans both rows, so it appears once on the left and reaches down past both drinks. The second row writes only its drink cell, because the spanned header already occupies that row's first column.

Count the Slots or the Grid Drifts

The one gotcha with spanning is arithmetic. The browser still tracks an invisible grid of rows and columns, and every spanned cell has to be paid for by the rows or columns it borrows from. If a row's normal cells plus its spanned cells do not add up to the table's column count, the grid drifts and cells land in the wrong place. When a row looks short, count the slots the spans already filled before you decide a cell is missing.

<table>
  <tr>
    <th colspan="2">Drinks</th>
    <th>Price</th>
  </tr>
  <tr>
    <td>Espresso</td>
    <td>Hot</td>
    <td>3.00</td>
  </tr>
</table>

The header row has two cells but they account for three columns, because the first th spans two. The data row writes three plain cells to match, so the grid stays three columns wide and everything lines up.

When to Use a Span

Reach for colspan or rowspan when one label genuinely describes a whole group of columns or rows, the way "Shift" genuinely labels both time columns in the schedule example below. That is different from using a span to patch over data that does not fit the grid cleanly, which is usually a sign that two separate tables, one for each shape, would serve the reader better than one table stretched to cover both.

Scope for Spanned Headers

The plain scope="col" and scope="row" values from the headers and captions guide only cover a header that labels a single column or row, and a spanned header covers more ground than that. scope="colgroup" tells assistive technology that the header labels an entire group of columns, and scope="rowgroup" does the same thing down a group of rows. The group values need the matching group structure in the markup: scope="colgroup" binds to <colgroup> elements that declare which columns belong together, while scope="rowgroup" binds to the row groups <thead>, <tbody>, and <tfoot> already give you. Use the plain values for a header that labels one column or row, and reach for the group values when a header genuinely heads a group, which is often what a colspan or rowspan on a header means. A spanning header in a simple table can still carry a plain scope="col" or scope="row", so match the value to what the header actually labels rather than applying the group values to every span.

Example

<table>
  <caption>Weekly schedule</caption>
  <colgroup></colgroup>
  <colgroup span="2"></colgroup>
  <thead>
    <tr>
      <th scope="col">Day</th>
      <th colspan="2" scope="colgroup">Shift</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th rowspan="2" scope="rowgroup">Monday</th>
      <td>Open</td>
      <td>9 to 1</td>
    </tr>
    <tr>
      <td>Close</td>
      <td>1 to 5</td>
    </tr>
  </tbody>
</table>

This renders as a table that is three columns wide. The "Shift" header spans the two right columns, and the "Monday" header runs down both data rows on the left. Each data row carries the cells it still owns, so the columns stay aligned even though two cells cover extra ground.

The two <colgroup> elements are what make scope="colgroup" mean something. That value only anchors a header to a real column group, so without a <colgroup> defining the two-column "Shift" group, assistive technology has nothing to tie the header to. The scope="rowgroup" header needs no extra markup, because the <tbody> already forms its row group.

Frequently Asked Questions

Can one cell span both rows and columns at the same time?

Yes. A cell may carry both colspan and rowspan, and it then occupies a rectangle of that width and height. Count the slots it consumes in every row it covers, because those rows need correspondingly fewer cells.

Does colspan work on th as well as td?

Yes. Both attributes are valid on th and td, and spanning header cells is common in grouped tables. A spanned header needs a scope value so assistive technology knows which cells it applies to.

What happens if colspan is larger than the number of columns?

The browser clamps it to the columns that exist rather than adding new ones, so the table renders but no longer matches your intent. It is a silent mistake, which is why counting the slots per row is worth the moment it takes.

Can you achieve spanning with CSS instead of HTML attributes?

CSS Grid can place an element across several tracks, which looks similar visually. It does not create the same relationships in the accessibility tree, so for tabular data keep colspan and rowspan in the markup where they carry meaning.

How do spanned cells behave on a narrow screen?

They keep their spans, which is often what makes a wide table unreadable on a phone. Reduce the number of columns before reaching for spanning, and let the table scroll rather than compressing spanned headers until the text wraps awkwardly.

Sources

  1. [1]
    The Table Data Cell element
    (developer.mozilla.org)
  2. [2]
    The Table Header element
    (developer.mozilla.org)
  3. [3]
  4. [4]