demo D117
Screen reader in React
What the grid announces, and the ARIA it writes
aria-rowindex · aria-selected
This grid shows what a screen reader announces and the ARIA it writes for a virtualised table whose DOM holds only the visible cells. It gives assistive technology correct row and column counts and state, so a grid that recycles rows still reads accurately.
This is the React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.
The configuration
<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">
<div id="grid"></div>
<script type="module">
import React from 'https://esm.sh/react@18';
import { createRoot } from 'https://esm.sh/react-dom@18/client';
import { createGrid } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.esm.min.js';
import createLatticeGrid from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/react.esm.min.js';
const LatticeGrid = createLatticeGrid({ React, createGrid });
// Roles, row and column indices, and sort state are all written, and
// aria-selected marks the selected rows for assistive technology.
const columns = [
{ field: 'service', title: 'Service', layout: { width: 150 } },
{ field: 'team', title: 'Team', filter: { type: 'set' } },
{ field: 'state', title: 'State' },
{ field: 'instances', title: 'Instances', type: 'number' },
{ field: 'uptime', title: 'Uptime', type: 'number', format: { style: 'percent', decimals: 3 } },
];
const rows = [/* service records */];
function App() {
const gridRef = React.useRef(null);
// Selected through the API so aria-selected is true on something: the
// attribute assistive technology reads, which no gesture currently sets.
React.useEffect(() => {
const grid = gridRef.current && gridRef.current.grid;
if (!grid) return;
const keys = [1, 2].map((i) => grid.rows.get(i)?.key).filter(Boolean);
if (keys.length) grid.selection.set(keys);
}, []);
return (
<LatticeGrid
ref={gridRef}
rowKey="id"
selection="multiple"
columns={columns}
rows={rows}
style={{ height: '540px' }}
/>
);
}
createRoot(document.getElementById('grid')).render(<App />);
</script>
What a screen reader hears when it lands on a grid
A JavaScript data grid that virtualises its rows presents a problem no static table has: the DOM only contains the cells currently on screen, so a screen reader’s row and column counts have to come from attributes, not from counting children. Lattice Grid writes aria-rowindex and aria-colindex on every rendered cell against the full logical size of the data, not the rendered window, so a user hears “row 4,208 of 1,000,000” even though row 4,208 is the only one of those in the DOM at that moment. Selection state is exposed the same way: aria-selected tracks the grid’s own selection model on the row and, where relevant, the cell, so a checkbox column and a screen reader announcement never drift apart. A developer reaches for this when a procurement checklist names WCAG or Section 508, or when support tickets show a user navigating with NVDA, JAWS or VoiceOver rather than a mouse. The grid role structure follows the ARIA grid pattern, with aria-rowcount and aria-colcount set on the container so assistive technology can announce position without walking the whole dataset, and it holds regardless of column virtualisation, so a wide grid with two hundred columns still reports the same indices for the ones currently off-screen.
Does a virtualised JavaScript data grid work with a screen reader?
It can, provided the rows and columns still on screen carry position information about the full dataset, not just the rendered subset. Lattice Grid sets aria-rowindex, aria-colindex, aria-rowcount and aria-colcount from the logical grid size, and keeps aria-selected in step with its selection model, so announcements stay correct as the DOM recycles during scrolling.