References

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

The HTML aria-colspan attribute

ARIA Accessibility
In short

The aria-colspan attribute gives assistive technology a cell's column span in a table built from ARIA roles. On <td> and <th>, use the HTML colspan attribute instead. The value is a whole number of at least 1, and the visual span is yours to create in CSS.

Overview

The aria-colspan attribute defines how many columns a cell or gridcell covers. It's intended for cells outside a native table, such as a div with role="cell", and the default is 1.

The value must be an integer of at least 1, and less than the value that would make the cell overlap the next cell in its row. A four-column row with one two-column cell therefore holds three cells.

On a real <td> or <th>, use colspan, and browsers must ignore aria-colspan where colspan is available. Avoid putting both on one element, and never give them different values, which doesn't conform.

A native cell is placed by colspan. With colspan="2" and aria-colspan="3" on one td, the cell's table span is 2, though Chrome's and Firefox's trees still list 3 among its accessibility attributes.

After a native colspan="2", the next cell reports column 3. After aria-colspan="2", Chrome and Firefox report column 2 until you give it aria-colindex="3". Under WAI-ARIA, a browser can derive that position, but in those two, cells after an ARIA span need aria-colindex.

Syntax

<div role="table" aria-label="Team calendar">
  <div role="row">
    <span role="cell" aria-colindex="1" aria-colspan="2">Trip</span>
    <span role="cell" aria-colindex="3">Office</span>
  </div>
</div>

Values

Value
An integer greater than or equal to 1.

Example

Live example
<p>Pick a length. The script sets aria-colspan and the CSS span together.</p>
<label for="days">Trip length</label>
<select id="days">
  <option value="1">1 day</option>
  <option value="2" selected>2 days</option>
  <option value="3">3 days</option>
  <option value="4">4 days</option>
</select>
<div role="table" aria-label="Team calendar" style="margin: 10px 0">
  <div role="row" style="display: grid; grid-template-columns: 3rem repeat(4, 1fr); gap: 2px; font-weight: bold">
    <span role="columnheader">Name</span>
    <span role="columnheader">Mon</span>
    <span role="columnheader">Tue</span>
    <span role="columnheader">Wed</span>
    <span role="columnheader">Thu</span>
  </div>
  <div role="row" style="display: grid; grid-template-columns: 3rem repeat(4, 1fr); gap: 2px">
    <span role="rowheader">Ana</span>
    <span role="cell" id="trip" aria-colindex="2" aria-colspan="2" style="grid-column: span 2; background: #dbeafe; color: #1e3a5f">Trip</span>
    <span role="cell" class="day" aria-colindex="3" hidden>Office</span>
    <span role="cell" class="day" aria-colindex="4">Office</span>
    <span role="cell" class="day" aria-colindex="5">Office</span>
  </div>
  <div role="row" style="display: grid; grid-template-columns: 3rem repeat(4, 1fr); gap: 2px">
    <span role="rowheader">Ben</span>
    <span role="cell" aria-colindex="2">Office</span>
    <span role="cell" aria-colindex="3">Office</span>
    <span role="cell" aria-colindex="4">Office</span>
    <span role="cell" aria-colindex="5">Office</span>
  </div>
</div>
<p role="status" id="state"></p>
<script>
const days = document.getElementById('days');
const trip = document.getElementById('trip');
const state = document.getElementById('state');
const cells = [...document.querySelectorAll('.day')];

function update() {
  const span = Number(days.value);
  trip.setAttribute('aria-colspan', span);
  trip.style.gridColumn = 'span ' + span;
  cells.forEach(cell => {
    cell.hidden = Number(cell.getAttribute('aria-colindex')) < 2 + span;
  });
  const next = cells.find(cell => !cell.hidden);
  state.textContent = next
    ? 'aria-colspan is ' + span + ', and the next cell keeps aria-colindex ' + next.getAttribute('aria-colindex') + '.'
    : 'aria-colspan is ' + span + ', so no cell follows in this row.';
}
document.querySelectorAll('[role="columnheader"]').forEach((cell, i) => cell.setAttribute('aria-colindex', i + 1));
document.querySelectorAll('[role="rowheader"]').forEach(cell => cell.setAttribute('aria-colindex', 1));
days.addEventListener('change', update);
update();
</script>

Best practices

  • Rule out a native table before you add aria-colspan. A <table> makes the markup less verbose and accessible by default.
  • Write the CSS span and the ARIA value from one number, in one function, as with grid-column: span 2 beside aria-colspan="2".
  • Take the cells a span covers out of the row, by removing them or setting hidden. Left in place, they'd break the rule against overlapping the next cell.
  • When a span grows or shrinks, check that each later cell's aria-colindex still matches the column it sits in.

Frequently asked questions

What is the difference between colspan and aria-colspan?
The HTML colspan attribute changes a table's layout, while aria-colspan describes a span to assistive technology. Use colspan on td and th, and aria-colspan on cells built from ARIA roles.
Does aria-colspan work on a td element?
No, aria-colspan has no effect on a td's table span, and browsers must ignore it there. A td with aria-colspan alone stays at a span of 1.
Can aria-colspan be 0?
No, aria-colspan must be at least 1, so 0 is invalid. Firefox lists a 0 with no change to the cell's table span, while Chrome's accessibility tree omits it.
Is there a maximum value for aria-colspan?
There's no maximum for aria-colspan, but the cell can't overlap the next one and shouldn't run past the table. HTML's colspan attribute, by comparison, must be 1000 or less.
Can a column header use aria-colspan?
Yes, columnheader and rowheader inherit aria-colspan from the cell role. Chrome and Firefox still give such a column header a table span of 1, so data cells under the rest of the span don't get it as their header, unlike cells under a native th with colspan.