Loading Developer Playground

Loading ...

Skip to main content
ARIA ATTRIBUTERelationship Attributes

aria-rowindex

Defines an element's row index or position with respect to the total number of rows within a table, grid, or treegrid.

Value Type
Integer (≥1)
Used With
row, cell, columnheader, rowheader
Index
1-based (starts at 1)

Overview

The aria-rowindex attribute defines a row's position within a table, grid, or treegrid. The value is a 1-based integer indicating the row's position in the complete dataset.

This attribute is essential for virtual scrolling implementations where only visible rows are rendered. It tells assistive technologies the true position of each row, enabling users to understand their location in large datasets.

You can set aria-rowindex on the row element itself (preferred) or on individual cells within the row.

Live Demo: Virtual Row Navigation

Showing rows 2 - 6 of 50
ID
Product
Category
Stock
row 3#002
Product 2
Home
24 units
row 4#003
Product 3
Sports
31 units
row 5#004
Product 4
Books
38 units
row 6#005
Product 5
Electronics
45 units
row 7#006
Product 6
Clothing
52 units

Each row has aria-rowindex indicating its position. Screen readers announce "Row 3 of 51" as users navigate, helping them understand their position in the full dataset.

Code Examples

Basic Usage

<!-- aria-rowindex indicates a row's position in the grid -->

<!-- Sparse table showing rows 1, 5, 10, and 15 of 20 -->
<table role="grid" aria-rowcount="20" aria-label="Quarterly reports">
  <thead>
    <tr aria-rowindex="1">
      <th>Quarter</th>
      <th>Revenue</th>
      <th>Growth</th>
    </tr>
  </thead>
  <tbody>
    <tr aria-rowindex="5">
      <td>Q1 2023</td>
      <td>$1.2M</td>
      <td>+5%</td>
    </tr>
    <tr aria-rowindex="10">
      <td>Q2 2023</td>
      <td>$1.4M</td>
      <td>+8%</td>
    </tr>
    <tr aria-rowindex="15">
      <td>Q3 2023</td>
      <td>$1.6M</td>
      <td>+12%</td>
    </tr>
  </tbody>
</table>

<!-- Screen reader: "Row 5 of 20, Q1 2023..." -->

Virtual Scrolling

<!-- Virtual scrolling list with aria-rowindex -->

<div 
  role="grid" 
  aria-rowcount="1000"
  aria-label="Message list"
>
  <!-- Only render visible rows in viewport -->
  <div role="row" aria-rowindex="245">
    <div role="gridcell">Message from Alice</div>
    <div role="gridcell">2024-01-15 10:30</div>
  </div>
  
  <div role="row" aria-rowindex="246">
    <div role="gridcell">Reply from Bob</div>
    <div role="gridcell">2024-01-15 10:35</div>
  </div>
  
  <div role="row" aria-rowindex="247">
    <div role="gridcell">Message from Carol</div>
    <div role="gridcell">2024-01-15 10:40</div>
  </div>
  
  <!-- ... more visible rows ... -->
</div>

Row vs Cell Placement

<!-- aria-rowindex on row element (preferred) -->

<div role="grid" aria-rowcount="100">
  <!-- rowindex on the row element -->
  <div role="row" aria-rowindex="25">
    <div role="gridcell">Cell A25</div>
    <div role="gridcell">Cell B25</div>
    <div role="gridcell">Cell C25</div>
  </div>
</div>

<!-- Alternative: aria-rowindex on each cell -->
<div role="grid" aria-rowcount="100">
  <div role="row">
    <div role="gridcell" aria-rowindex="25">Cell A25</div>
    <div role="gridcell" aria-rowindex="25">Cell B25</div>
    <div role="gridcell" aria-rowindex="25">Cell C25</div>
  </div>
</div>

<!-- Note: Prefer setting on row element for cleaner markup -->

React Component

// React virtual grid with aria-rowindex
import { useState, useCallback, useRef } from 'react';

interface GridRow {
  id: string;
  data: string[];
}

interface VirtualGridProps {
  rows: GridRow[];
  totalRows: number;
  rowHeight: number;
  containerHeight: number;
  columns: string[];
  label: string;
}

function VirtualGrid({
  rows,
  totalRows,
  rowHeight,
  containerHeight,
  columns,
  label
}: VirtualGridProps) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef<HTMLDivElement>(null);
  
  // Calculate visible range
  const visibleRowCount = Math.ceil(containerHeight / rowHeight) + 1;
  const startIndex = Math.floor(scrollTop / rowHeight);
  const endIndex = Math.min(startIndex + visibleRowCount, rows.length);
  
  const visibleRows = rows.slice(startIndex, endIndex);
  
  const handleScroll = useCallback((e: React.UIEvent) => {
    setScrollTop(e.currentTarget.scrollTop);
  }, []);

  // Handle keyboard navigation
  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    const container = containerRef.current;
    if (!container) return;
    
    const activeRow = document.activeElement?.closest('[role="row"]');
    if (!activeRow) return;
    
    const currentIndex = parseInt(
      activeRow.getAttribute('aria-rowindex') || '1'
    );
    
    switch (e.key) {
      case 'ArrowDown':
        // Navigate to next row
        const nextIndex = Math.min(currentIndex + 1, totalRows);
        // Scroll if needed and focus next row
        e.preventDefault();
        break;
      case 'ArrowUp':
        // Navigate to previous row
        const prevIndex = Math.max(currentIndex - 1, 1);
        e.preventDefault();
        break;
      case 'Home':
        if (e.ctrlKey) {
          // Go to first row
          setScrollTop(0);
          e.preventDefault();
        }
        break;
      case 'End':
        if (e.ctrlKey) {
          // Go to last row
          setScrollTop((totalRows - visibleRowCount) * rowHeight);
          e.preventDefault();
        }
        break;
    }
  }, [totalRows, rowHeight, visibleRowCount]);

  return (
    <div
      ref={containerRef}
      role="grid"
      aria-rowcount={totalRows + 1} // +1 for header
      aria-label={label}
      style={{ height: containerHeight, overflowY: 'auto' }}
      onScroll={handleScroll}
      onKeyDown={handleKeyDown}
    >
      {/* Header row */}
      <div role="row" aria-rowindex={1} style={{ height: rowHeight }}>
        {columns.map((col, i) => (
          <div key={i} role="columnheader">
            {col}
          </div>
        ))}
      </div>
      
      {/* Spacer above visible rows */}
      <div style={{ height: startIndex * rowHeight }} />
      
      {/* Visible rows */}
      {visibleRows.map((row, idx) => {
        const actualRowIndex = startIndex + idx + 2; // +2 for header and 1-based
        return (
          <div
            key={row.id}
            role="row"
            aria-rowindex={actualRowIndex}
            style={{ height: rowHeight }}
            tabIndex={0}
          >
            {row.data.map((cell, cellIdx) => (
              <div key={cellIdx} role="gridcell">
                {cell}
              </div>
            ))}
          </div>
        );
      })}
      
      {/* Spacer below visible rows */}
      <div style={{ height: (rows.length - endIndex) * rowHeight }} />
    </div>
  );
}

Related Attributes

Specifications & Resources