References

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

The HTML aria-rowindex attribute

ARIA Accessibility
In short

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

Live 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?
No, aria-rowindex is a position you declare, counted from 1 across the full table. The DOM's 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.
Can aria-rowindex start at a number above 1?
Yes, the first rendered row takes the aria-rowindex of its place in the full table. A view scrolled to row 80 gives its first body row aria-rowindex 80.
Can two rows share the same aria-rowindex?
No, each row's aria-rowindex must be greater than that of any previous row. Cells are different, since a cell repeats the number of its row.
Do you need aria-rowcount when you use aria-rowindex?
Yes, a partial table with aria-rowindex needs aria-rowcount on the table as well. That holds even if you don't know the total, which is what the value -1 is for.
Does aria-rowindex replace aria-posinset in a treegrid?
No, aria-rowindex gives a row's place in the whole treegrid, while aria-posinset belongs to the tree hierarchy. A row in a table or grid must not carry aria-posinset.