aria-rowcount
Defines the total number of rows in a table, grid, or treegrid when not all rows are present in the DOM.
Overview
The aria-rowcount attribute defines the total number of rows in a table, grid, or treegrid. It's essential for virtual scrolling implementations where only a subset of rows are rendered in the DOM.
When all rows are present, browsers calculate the count automatically. But for large datasets with thousands of rows, rendering everything hurts performance. Use aria-rowcount to tell assistive technologies the true total.
Use aria-rowcount="-1" when the total is unknown (infinite scroll, lazy loading).
Live Demo: Virtual Scrolling Table
| ID | Name | Status | Progress |
|---|---|---|---|
| 002 | Item 2 | Pending | 20% |
| 003 | Item 3 | Complete | 30% |
| 004 | Item 4 | In Progress | 40% |
| 005 | Item 5 | Pending | 50% |
| 006 | Item 6 | Complete | 60% |
aria-rowcount="101" (100 data rows + 1 header) tells screen readers the full table size. Each row uses aria-rowindex to indicate its position.
Code Examples
Basic Usage
<!-- aria-rowcount indicates total rows when not all are in DOM -->
<!-- Table with 1000 total rows, only 10 visible -->
<table role="grid" aria-rowcount="1000" aria-label="Customer list">
<thead>
<tr aria-rowindex="1">
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<!-- Showing rows 50-59 of 1000 -->
<tr aria-rowindex="50">
<td>050</td>
<td>Customer 50</td>
<td>customer50@example.com</td>
</tr>
<tr aria-rowindex="51">
<td>051</td>
<td>Customer 51</td>
<td>customer51@example.com</td>
</tr>
<!-- ... more visible rows ... -->
</tbody>
</table>
<!-- Screen reader announces: "Table with 1000 rows" -->Virtual Scrolling
<!-- Virtual scrolling grid with many rows -->
<div
role="grid"
aria-rowcount="10000"
aria-colcount="5"
aria-label="Large dataset"
>
<!-- Header row -->
<div role="row" aria-rowindex="1">
<div role="columnheader">ID</div>
<div role="columnheader">Name</div>
<div role="columnheader">Date</div>
<div role="columnheader">Amount</div>
<div role="columnheader">Status</div>
</div>
<!-- Only render visible rows (e.g., rows 500-510) -->
<div role="row" aria-rowindex="500">
<div role="gridcell">500</div>
<div role="gridcell">Transaction 500</div>
<div role="gridcell">2024-01-15</div>
<div role="gridcell">$1,250.00</div>
<div role="gridcell">Complete</div>
</div>
<div role="row" aria-rowindex="501">
<div role="gridcell">501</div>
<div role="gridcell">Transaction 501</div>
<div role="gridcell">2024-01-15</div>
<div role="gridcell">$890.00</div>
<div role="gridcell">Pending</div>
</div>
<!-- ... rows 502-510 ... -->
</div>Unknown Row Count
<!-- When total row count is unknown -->
<!-- Use aria-rowcount="-1" for dynamic/unknown totals -->
<table role="grid" aria-rowcount="-1" aria-label="Search results">
<thead>
<tr aria-rowindex="1">
<th>Result</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<!-- Rows load as user scrolls -->
<tr aria-rowindex="2">
<td>Result 1</td>
<td>Description 1</td>
</tr>
<tr aria-rowindex="3">
<td>Result 2</td>
<td>Description 2</td>
</tr>
</tbody>
</table>
<!-- -1 indicates "count unknown" to assistive technology -->
<!-- Use when implementing infinite scroll or lazy loading -->React Component
// React virtual list with aria-rowcount
import { useState, useCallback, useRef, useEffect } from 'react';
interface VirtualListProps<T> {
items: T[];
totalItems: number;
itemHeight: number;
containerHeight: number;
renderItem: (item: T, index: number) => React.ReactNode;
label: string;
}
function VirtualList<T>({
items,
totalItems,
itemHeight,
containerHeight,
renderItem,
label
}: VirtualListProps<T>) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
const visibleCount = Math.ceil(containerHeight / itemHeight);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + visibleCount + 1, items.length);
const visibleItems = items.slice(startIndex, endIndex);
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
setScrollTop(e.currentTarget.scrollTop);
}, []);
return (
<div
ref={containerRef}
role="grid"
aria-rowcount={totalItems}
aria-label={label}
style={{ height: containerHeight, overflowY: 'auto' }}
onScroll={handleScroll}
>
{/* Spacer for items above viewport */}
<div style={{ height: startIndex * itemHeight }} />
{visibleItems.map((item, idx) => {
const actualIndex = startIndex + idx;
return (
<div
key={actualIndex}
role="row"
aria-rowindex={actualIndex + 1} // 1-based
style={{ height: itemHeight }}
>
{renderItem(item, actualIndex)}
</div>
);
})}
{/* Spacer for items below viewport */}
<div style={{ height: (items.length - endIndex) * itemHeight }} />
</div>
);
}
// Usage
function DataGrid() {
const [data, setData] = useState<string[]>([]);
const [totalCount, setTotalCount] = useState(-1); // Unknown initially
useEffect(() => {
// Fetch total count and initial data
fetchData().then(result => {
setData(result.items);
setTotalCount(result.total);
});
}, []);
return (
<VirtualList
items={data}
totalItems={totalCount}
itemHeight={50}
containerHeight={400}
label="Data grid"
renderItem={(item, index) => (
<div role="gridcell">{item}</div>
)}
/>
);
}
