The grid paints to a <canvas>, which assistive technology can not read. To make
it navigable, Sheetwrite maintains a parallel ARIA shadow tree: a
visually-hidden DOM mirror of the visible window that carries the grid semantics,
while the canvas itself is hidden from the accessibility tree.
The host grid
createGrid annotates the host element as the grid:
| Attribute | Value |
|---|---|
role | "grid" |
aria-multiselectable | "true" |
aria-rowcount | total rows + 1 (the column-letter header counts as a row) |
aria-colcount | number of visible columns |
aria-readonly | "true" when the grid is readOnly (omitted otherwise) |
aria-label | "Spreadsheet grid" (default; not overwritten if you set your own) |
aria-activedescendant | the id of the focused cell in the mirror |
The visual layers — the scroller, the overlay, and the <canvas> — are all marked
aria-hidden="true", so screen readers ignore the pixels and read the mirror.
The SR-only mirror
Alongside the host the grid appends a visually-hidden <div class="sheetwrite-aria">
with a unique id (sheetwrite-grid-<n>) and role="rowgroup". It contains:
- A header row —
role="row",aria-rowindex="1"— whose cells arerole="columnheader"with a 1-basedaria-colindex. Spreadsheet presentation uses the positional column letter (A,B, …); data-grid presentation uses the semanticColumn.header. - One data row per visible row —
role="row"witharia-rowindexset to the true row position (data row + 2, leaving index 1 for the header). Its cells arerole="gridcell"with a 1-basedaria-colindex, a stable id (<gridId>-<row>-<col>), and the cell's value as text. - Every visible cell covered by the current cell/range/multi selection carries
aria-selected="true". A row selection marks its visible row; a column selection marks both its header and visible cells. The focused cell's id is mirrored into the host'saria-activedescendant.
Sketch of the emitted structure:
<div role="grid" aria-multiselectable="true" aria-rowcount="100001" aria-colcount="5" aria-activedescendant="sheetwrite-grid-1-0-0" tabindex="0"> <!-- canvas / scroller / overlay, all aria-hidden --> <div class="sheetwrite-aria" id="sheetwrite-grid-1" role="rowgroup"> <div role="row" aria-rowindex="1"> <div role="columnheader" aria-colindex="1">A</div> <div role="columnheader" aria-colindex="2">B</div> </div> <div role="row" aria-rowindex="2"> <div role="gridcell" aria-colindex="1" id="sheetwrite-grid-1-0-0" aria-selected="true">Widget</div> <div role="gridcell" aria-colindex="2" id="sheetwrite-grid-1-0-1">12</div> </div> <!-- … one row per visible row … --> </div></div>As keyboard or pointer input changes the selection, aria-selected and the
host's aria-activedescendant update. Scrolling rebuilds the visible mirror.
Horizontal virtualization does not renumber the mirror: if the visible window
starts at sheet column G, its header and cells use aria-colindex="7". Frozen
and scrolling columns likewise keep their absolute one-based sheet positions.
A focused cell note is connected through aria-describedby to hidden plain text.
Validation list and checkbox editors use real listbox / option and checkbox
semantics, with the rule help text as their accessible label.
Host-supplied editors receive a context.label composed from that same
semantic/positional header and row number. Core applies it to the retained editor
wrapper and to the first focusable control when the editor did not provide its
own name. Commit, cancel, reset, and unmount return focus to the grid; teardown
aborts the editor signal before removing its DOM.
Custom DOM renderer output uses the same mirror for its default cell value. Non-semantic renderer elements are hidden from assistive technology to avoid a duplicate announcement. A renderer can expose an explicitly interactive, labelled control; its keyboard events remain with that control, its element is retained across scroll and geometry updates, and focus returns to the grid if virtualization or renderer replacement removes it.
Limitation: the mirror reflects the visible window
The mirror contains only the rows and columns currently rendered (the same virtualized window the canvas paints, plus overscan) — not the entire sheet. This is deliberate: materializing 100k+ rows of DOM would defeat the point of canvas rendering.
The consequences to be aware of:
aria-rowindex/aria-colindexcarry the true positions, so assistive technology can announce "row N ofaria-rowcount" correctly even though only a window is present.- A screen reader's own virtual cursor can only walk the cells in the rendered
window. Use the grid's keyboard navigation (arrows, Page Up/Down, Home/End — see
Interaction) to move focus; that scrolls
new rows into view and into the mirror, and updates
aria-activedescendant.