References

Beginner-friendly references for web development, with live, editable examples.

The HTML aria-rowspan attribute

ARIA Accessibility
In short

Give an ARIA cell aria-rowspan="2" when it reaches down two rows. A value of 0 means all the remaining rows in the row group. Native <td> and <th> cells leave that job to the HTML rowspan attribute.

Overview

The aria-rowspan attribute states the vertical span of a cell or gridcell, in rows, with a default of 1. Its unusual value is 0, meaning all the remaining rows in the row group. Only non-negative integers are allowed, and the span has to stop short of the next cell in its column.

HTML's rowspan="0" means the same thing, while aria-colspan starts at 1. Browsers don't treat the ARIA 0 alike. Chrome leaves it out of the accessibility tree, and Firefox passes it along as an attribute. A number such as 2 appears in both, so state the count when you know it.

Rows below a span leave out the cells it covers. In a four-column row where two cells have aria-rowspan="2", the next row holds two cells instead of four. Chrome and Firefox don't skip covered columns. Under a span in column 1, the next row's column 2 cell reports column 1 without aria-colindex.

A <td> or <th> follows rowspan, which takes precedence over the ARIA attribute when both are present. Adding aria-rowspan="1" to a cell with rowspan="2" changes nothing in the accessibility tree's table data, where the cell still spans two rows.

On a native cell or one built from roles, aria-rowspan has no visual effect. ARIA changes the accessibility tree, not an element's function, behavior or appearance, so cells built from roles need CSS for their layout.

Syntax

<div role="table" aria-label="Room schedule">
  <div role="row">
    <span role="cell" aria-colindex="1" aria-rowspan="2">Workshop</span>
    <span role="cell" aria-colindex="2">Standup</span>
  </div>
  <div role="row">
    <span role="cell" aria-colindex="2">Review</span>
  </div>
</div>

Values

Value
An integer greater than or equal to 0.

Example

Live example
<p style="margin: 0 0 6px">Change the value and watch the Workshop cell.</p>
<label for="span">Workshop aria-rowspan</label>
<select id="span">
  <option value="1">1</option>
  <option value="2" selected>2</option>
  <option value="3">3</option>
  <option value="0">0 (rest of the group)</option>
</select>
<div role="table" aria-label="Room schedule" style="display: grid; grid-template-columns: 3.5rem 1fr 1fr; gap: 2px; margin: 6px 0">
  <div role="rowgroup" style="display: contents">
    <div role="row" style="display: contents; font-weight: bold">
      <span role="columnheader">Time</span>
      <span role="columnheader">Room A</span>
      <span role="columnheader">Room B</span>
    </div>
  </div>
  <div role="rowgroup" style="display: contents">
    <div role="row" style="display: contents">
      <span role="rowheader">9:00</span>
      <span role="cell" id="talk" aria-colindex="2" aria-rowspan="2" style="grid-row: span 2; background: #dbeafe; color: #1e3a5f">Workshop</span>
      <span role="cell" aria-colindex="3">Standup</span>
    </div>
    <div role="row" style="display: contents">
      <span role="rowheader">10:00</span>
      <span role="cell" class="free" data-row="2" aria-colindex="2" hidden>Free</span>
      <span role="cell" aria-colindex="3">Review</span>
    </div>
    <div role="row" style="display: contents">
      <span role="rowheader">11:00</span>
      <span role="cell" class="free" data-row="3" aria-colindex="2">Free</span>
      <span role="cell" aria-colindex="3">Planning</span>
    </div>
  </div>
</div>
<p role="status" id="state" style="margin: 0"></p>
<script>
const span = document.getElementById('span');
const talk = document.getElementById('talk');
const state = document.getElementById('state');
const free = [...document.querySelectorAll('.free')];

function update() {
  const value = span.value;
  const rows = value === '0' ? 3 : Number(value);
  talk.setAttribute('aria-rowspan', value);
  talk.style.gridRow = 'span ' + rows;
  free.forEach(cell => {
    cell.hidden = Number(cell.dataset.row) <= rows;
  });
  state.textContent = value === '0'
    ? 'aria-rowspan is 0, meaning all remaining rows in this row group. The script sets grid-row: span 3.'
    : 'aria-rowspan is ' + value + ', and grid-row: span ' + value + ' draws it. Room B cells keep aria-colindex 3.';
}
document.querySelectorAll('[role="columnheader"]').forEach((cell, i) => cell.setAttribute('aria-colindex', i + 1));
document.querySelectorAll('[role="rowheader"]').forEach(cell => cell.setAttribute('aria-colindex', 1));
span.addEventListener('change', update);
update();
</script>

Best practices

  • Drop aria-rowspan from a td or th that has rowspan. That cell's span is already stated, and the two must not disagree.
  • Recount the span when rows are added or removed below it, because a fixed value can run into the next cell in the column.
  • Keep the CSS span equal to the ARIA value. CSS has no matching 0, so with aria-rowspan="0" your script works out the remaining rows itself.
  • In a table built from roles, number each cell with aria-colindex once a cell spans rows. The row-level shortcut for aria-colindex covers only sets with no spans.

Frequently asked questions

What does aria-rowspan=0 mean?
Setting aria-rowspan to 0 says the cell spans all the remaining rows in its row group. A row group is an element with the rowgroup role, the ARIA equivalent of thead, tbody and tfoot.
Should an aria-rowspan cell carry aria-rowindex too?
Yes, when rows are missing from the DOM, put aria-rowindex on the aria-rowspan cell too. Number it by the first row it covers.
Does aria-rowspan change a cell's rowSpan property?
No, rowSpan reflects only the HTML rowspan attribute, so a td with aria-rowspan set to 3 still reports 1. Read the ARIA value from the ariaRowSpan property.
Which CSS draws an aria-rowspan cell across rows?
A grid-row span inside one shared CSS grid is one way to draw an aria-rowspan cell. Setting each row's display to contents puts its cells on that grid, and Chrome and Firefox still expose those rows.
Can aria-rowspan and aria-colspan go on the same cell?
Yes, aria-rowspan and aria-colspan can share a cell, because the cell role supports both. The cell then covers a block of rows and columns.