The HTML aria-colindex attribute
Use aria-colindex to give a cell its real column number, from 1, when columns are missing from the DOM. If the columns you keep have no gaps and no cell spans, one value on each row is allowed. Otherwise each cell should have its own, as in aria-colindex="5".
Overview
The aria-colindex attribute states an element's column position within the full table, grid or treegrid, counting from 1. It belongs to the cell and row roles, and columnheader, gridcell and rowheader get it from cell.
While every column is in the DOM, the browser can work out each position itself, according to WAI-ARIA. Remove columns 3 and 4, though, and the old column 5 looks like column 3. The attribute is needed then, and aria-colcount supplies the total it counts against.
If the columns you keep are contiguous, with no gaps, and no cell spans rows or columns, you may put it on each row, set to the first column of the set. Chrome's tree numbers that row's cells upward from the value, while Firefox exposes it on the row and its cells keep their DOM positions.
In other cases, it should go on all of the row's children. That covers gaps, such as columns 2, 3, 4 and 9, and sets that contain a span.
A header cell doesn't speak for its column. Setting it on <th> cells alone isn't enough, because the <td> cells below don't pick it up. Chrome and Firefox still report column 2 for the second td when the th above it has aria-colindex="5", so repeat the value on each td.
Syntax
<div role="grid" aria-label="Contacts" aria-colcount="6">
<div role="row">
<span role="gridcell" aria-colindex="1">Ana Silva</span>
<span role="gridcell" aria-colindex="5">Lisbon</span>
</div>
</div>
Values
| Value |
|---|
| An integer greater than or equal to 1. |
Example
<p>Move through the months, then pin Region to create a gap.</p>
<p>
<button id="back">Earlier</button>
<button id="forward">Later</button>
<label><input type="checkbox" id="pin"> Pin Region</label>
</p>
<table id="sales" aria-colcount="13" aria-label="Sales by month" style="border-collapse: collapse">
<thead id="head"></thead>
<tbody id="body"></tbody>
</table>
<p role="status" id="state"></p>
<script>
const names = ['Region', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const regions = ['North', 'South'];
const head = document.getElementById('head');
const body = document.getElementById('body');
const pin = document.getElementById('pin');
const state = document.getElementById('state');
let first = 2;
function fill(row, kept, tag, textFor, onCells) {
kept.forEach(column => {
const cell = document.createElement(tag);
cell.textContent = textFor(column);
cell.style.padding = '2px 8px';
if (onCells) cell.setAttribute('aria-colindex', column);
row.appendChild(cell);
});
}
function render() {
const months = [first, first + 1, first + 2];
const kept = pin.checked ? [1, ...months] : months;
const contiguous = kept.every((column, i) => i === 0 || column === kept[i - 1] + 1);
head.replaceChildren();
body.replaceChildren();
const rows = [head.insertRow()];
regions.forEach(() => rows.push(body.insertRow()));
rows.forEach((row, r) => {
if (contiguous) row.setAttribute('aria-colindex', kept[0]);
fill(row, kept, r === 0 ? 'th' : 'td', column => {
if (r === 0) return names[column - 1];
return column === 1 ? regions[r - 1] : String(20 + (column * 7 + r * 11) % 30);
}, !contiguous);
});
state.textContent = contiguous
? 'Columns ' + kept[0] + ' to ' + kept[kept.length - 1] + ' have no gap, so each row carries aria-colindex ' + kept[0] + '.'
: 'Columns ' + kept.join(', ') + ' have a gap, so each cell carries its own aria-colindex.';
}
document.getElementById('back').addEventListener('click', () => {
first = Math.max(2, first - 1);
render();
});
document.getElementById('forward').addEventListener('click', () => {
first = Math.min(11, first + 1);
render();
});
pin.addEventListener('change', render);
render();
</script>
Best practices
- Compute aria-colindex before you skip any columns, so a column you leave out still uses up its number.
- Give a row-level value the number of the first rendered column. With columns 5 to 7 in the DOM, each row carries 5.
- Clear row-level values when you switch to per-cell values, so the two forms don't disagree.
- Pair it with aria-colindextext, a WAI-ARIA 1.3 draft attribute, when a spreadsheet labels its columns with letters.
- Add aria-colspan to a spanning cell in a table built from roles. On cells that aren't
tdorthelements, aria-colspan states the span and aria-colindex the start.
Frequently asked questions
Is aria-colindex zero-based or one-based?
Can aria-colindex values skip numbers?
What aria-colindex does a cell spanning several columns take?
How does aria-colindex differ from the cellIndex property?
cellIndex of 1, because the two count different things. The attribute's numbers start at 1 for the full table, while the property starts at 0 among the row's cells, which doesn't necessarily match the cell's column in the table.






















