Lattice Grid Buy a licence

demo D117

Screen reader

What the grid announces, and the ARIA it writes

aria-rowindex · aria-selected

Building…
Loading a live grid…

The configuration

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.27.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.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.27.0/lattice-grid.esm.min.js';
  import createLatticeGrid from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.27.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.