demo D117
Screen reader
What the grid announces, and the ARIA it writes
aria-rowindex · aria-selected
The configuration
import * as ng from '@angular/core';
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import createLatticeGrid from '@toclocoinc/lattice-grid/modules/angular';
const { LatticeGridComponent } = createLatticeGrid({ ng, 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 } },
];
@Component({
selector: 'app-root',
standalone: true,
imports: [LatticeGridComponent],
template: '<lattice-grid [config]="grid" style="display:block;height:540px"></lattice-grid>',
})
export class AppComponent implements AfterViewInit {
@ViewChild(LatticeGridComponent) gridRef!: LatticeGridComponent;
grid = {
rowKey: 'id',
selection: 'multiple',
columns,
rows: [/* service records */],
};
// Selected through the API so aria-selected is true on something: the attribute
// assistive technology reads, which no gesture currently sets.
ngAfterViewInit() {
const grid = this.gridRef && this.gridRef.grid;
if (!grid) return;
const keys = [1, 2].map((i) => grid.rows.get(i)?.key).filter(Boolean);
if (keys.length) grid.selection.set(keys);
}
}
bootstrapApplication(AppComponent);
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.