The HTML aria-rowindex attribute
Row 250 of a 1,000-row table gets aria-rowindex="250", even when it's the second row in the DOM. The value counts from 1 across the full table, so assistive technology can get the real position when rows are missing. Set it on each row, and give the table aria-rowcount.
Overview
The aria-rowindex attribute numbers a row by its place among all the rows in the full table, from 1. It's for tables, grids and treegrids with rows missing from the DOM. A table that loads rows 100 to 102 of 2,000 uses it so those rows report 100, 101 and 102.
Rows should carry the number, and the row's children are an optional extra place, the reverse of the pattern for aria-colindex. On a native table, that means <tr>, because its implicit role is row.
In Chrome's accessibility tree, a row's value reaches its cells and the first cell's value reaches its row, while a value on a later cell alone is lost. Firefox keeps it where you wrote it, so a number set only on cells leaves the row without one.
Each value must be a whole number of 1 or more, it must climb from one row to the next, and it can't pass the number of rows in the full table. Gaps are expected, so a header row at 1 can sit directly above a row at 100.
Give a cell that spans rows the attribute itself, which is optional on other cells, and set it to the row where the span starts. That holds whether the span comes from aria-rowspan or a native rowspan.
Syntax
<div role="table" aria-label="Shipments" aria-rowcount="500">
<div role="row" aria-rowindex="250">
<div role="cell">SH-250</div>
</div>
</div>
Values
| Value |
|---|
| An integer greater than or equal to 1. |
Example
<form id="jump" novalidate>
<label for="go">Jump to row (2 to 499)</label>
<input id="go" type="number" min="2" max="499" value="250" style="width: 5em">
<button>Show</button>
</form>
<table id="ship" aria-rowcount="500" aria-label="Shipments" style="border-collapse: collapse; margin-top: 8px">
<thead><tr id="head"><th scope="col">Shipment</th><th scope="col">Status</th></tr></thead>
<tbody id="rows"></tbody>
</table>
<p role="status" id="state"></p>
<script>
const statuses = ['Packed', 'In transit', 'Delivered'];
const input = document.getElementById('go');
const rows = document.getElementById('rows');
const state = document.getElementById('state');
function show() {
const n = Number(input.value);
if (input.value === '' || !Number.isInteger(n) || n < 2 || n > 499) {
state.textContent = 'Enter a whole number from 2 to 499.';
return;
}
rows.replaceChildren();
[n, n + 1].forEach(position => {
const row = rows.insertRow();
row.setAttribute('aria-rowindex', position);
row.insertCell().textContent = 'SH-' + position;
row.insertCell().textContent = statuses[position % 3];
});
const row = rows.rows[0];
state.textContent = 'Row ' + n + ' of 500 has aria-rowindex ' + row.getAttribute('aria-rowindex') + ', while its tr.rowIndex is ' + row.rowIndex + '.';
}
document.getElementById('head').setAttribute('aria-rowindex', 1);
document.getElementById('jump').addEventListener('submit', event => {
event.preventDefault();
show();
});
show();
</script>
Best practices
- Start from the full data set when you compute aria-rowindex, and add any header rows above it. Forgetting the header makes each number one too low.
- Rewrite the value when a virtual scroller reuses a row element, so the row reports the position of its new data.
- Give a footer row the last index in the table, such as
aria-rowindex="1002"when aria-rowcount is 1002, because the index counts every row in the full table. - You can leave it off a table that renders all of its rows, because the browser numbers them itself. Rows that carry aria-rowindextext should still keep the number beside it.
Frequently asked questions
Is aria-rowindex the same as tr.rowIndex?
rowIndex property is the row's place in the table's rows collection, counted from 0, so it can't see rows outside the DOM.






















