Understand the ARIA grid mirror, keyboard behavior, focus, and visible-window accessibility limits.On this page
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.
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 are
role="columnheader" with a 1-based aria-colindex. Spreadsheet presentation
uses the positional column letter (A, B, …); data-grid presentation uses
the semantic Column.header.
One data row per visible row — role="row" with aria-rowindex set to the
true row position (data row + 2, leaving index 1 for the header). Its cells are
role="gridcell" with a 1-based aria-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's aria-activedescendant.
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-colindex carry the true positions, so assistive
technology can announce "row N of aria-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.
const grid: Gridconstgrid:Gridconstgrid:GridThe imperative core grid this controller owns.API reference →Grid.setSelection(sel: Selection | null): voidGrid.setSelection(sel: Selection |null): voidGrid.setSelection(sel: Selection |null): voidkind: "cell"let kind:"cell"let kind:"cell"Selects one addressed cell.addr: CellAddresslet addr:CellAddresslet addr:CellAddressZero-based data address of the selected cell.API reference →CellAddress.sheet: stringinterfaceCellAddress {sheet:string;}interfaceCellAddress {sheet:string;}Sheet identity used by the simple data-first adapter. Defaults to sheet1.API reference →CellAddress.row: numberinterfaceCellAddress {row:number;}interfaceCellAddress {row:number;}0-based row of the cell that holds the top-left corner.API reference →CellAddress.col: numberinterfaceCellAddress {col:number;}interfaceCellAddress {col:number;}0-based column of the cell that holds the top-left corner.API reference →function requestAnimationFrame(callback: FrameRequestCallback): numberfunctionrequestAnimationFrame(callback:FrameRequestCallback,):numberfunctionrequestAnimationFrame(callback:FrameRequestCallback,):numberMDN Referenceconst mirror: Element | nullconstmirror:Element|nullconstmirror:Element|nullconst host: HTMLElementconsthost:HTMLElementconsthost:HTMLElementParentNode.querySelector<Element>(selectors: string): Element | null (+4 overloads)ParentNode.querySelector<Element>(selectors: string): Element |null (+4 overloads)ParentNode.querySelector<Element>(selectors: string): Element |null (+4 overloads)Returns the first element that is a descendant of node that matches selectors. MDN Referencevar console: Consolevar console:Consolevar console:ConsoleConsole.log(...data: any[]): void (+1 overload)Console.log(...data: any[]): void (+1 overload)Console.log(...data: any[]): void (+1 overload)The console.log() static method outputs a message to the console. MDN ReferenceElement.getAttribute(qualifiedName: string): string | nullElement.getAttribute(qualifiedName: string): string |nullElement.getAttribute(qualifiedName: string): string |nullThe getAttribute() method of the element. MDN ReferenceParentNode.querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]> (+4 overloads)ParentNode.querySelectorAll<KextendskeyofHTMLElementTagNameMap>(selectors:K):NodeListOf<HTMLElementTagNameMap[K]> (+4overloads)ParentNode.querySelectorAll<KextendskeyofHTMLElementTagNameMap>(selectors:K):NodeListOf<HTMLElementTagNameMap[K]> (+4overloads)Returns all element descendants of node that match selectors. MDN ReferenceNodeList.length: number | undefinedinterfaceNodeList {length:number|undefined;}interfaceNodeList {length:number|undefined;}The NodeList.length property returns the number of items in a NodeList. MDN Referencefunction createGrid(host: HTMLElement, opts: GridOptions): GridfunctioncreateGrid(host:HTMLElement,opts:GridOptions,):GridfunctioncreateGrid(host:HTMLElement,opts:GridOptions,):GridCreates and mounts an imperative Grid in the supplied host element.Exampleimport { createGrid, initSheetwrite } from"@sheetwrite/core";awaitinitSheetwrite();consthost= document.createElement("div");host.style.height ="400px";document.body.append(host);constgrid=createGrid(host, {workbook: {activeSheet: "sales",sheets: [{ id: "sales", name: "Sales", rowCount: 2,columns: [{ key: "amount", header: "Amount", type: "number", width: 120 }] }],},data: { rowCount: 2, columns: { amount: [12, 18] } },});grid.scrollToCell({ sheet: "sales", row: 1, col: 0 });// Call when the host view is removed.grid.destroy();See alsoGrid *See alsoGridOptionsAPI reference →function initSheetwrite(source?: BufferSource | URL | string | Request | WebAssembly.Module, engine?: SheetwriteEngine): Promise<void>functioninitSheetwrite(source?:BufferSource|URL|string|Request|WebAssembly.Module,engine?:SheetwriteEngine,):Promise<void>functioninitSheetwrite(source?:|BufferSource|URL|string|Request|WebAssembly.Module,engine?:SheetwriteEngine,):Promise<void>Load one WASM engine before createGrid. The default is @sheetwrite/wasm. Pass the @sheetwrite/formulas module as the second argument to select the full engine.Exampleimport { initSheetwrite } from"@sheetwrite/core";import*as formulas from"@sheetwrite/formulas";awaitinitSheetwrite(undefined, formulas);See also/docs/start/choose-an-engine/See alsocreateGridAPI reference →const host: HTMLElement | nullconsthost:HTMLElement|nullconsthost:HTMLElement|nullvar document: Documentvar document:Documentvar document:Documentwindow.document returns a reference to the document contained in the window. MDN ReferenceAPI reference →ParentNode.querySelector<HTMLElement>(selectors: string): HTMLElement | null (+4 overloads)ParentNode.querySelector<HTMLElement>(selectors: string): HTMLElement |null (+4 overloads)ParentNode.querySelector<HTMLElement>(selectors: string): HTMLElement |null (+4 overloads)Returns the first element that is a descendant of node that matches selectors. MDN Referenceinterface HTMLElementinterfaceHTMLElementinterfaceHTMLElementThe HTMLElement interface represents any HTML element. MDN Referencevar Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+2 overloads)var Error:ErrorConstructornew (message?:string, options?:ErrorOptions) =>Error (+2 overloads)var Error:ErrorConstructornew (message?:string, options?:ErrorOptions) =>Error (+2 overloads)ElementCSSInlineStyle.style: CSSStyleDeclarationinterfaceElementCSSInlineStyle {style:CSSStyleDeclaration;}interfaceElementCSSInlineStyle {style:CSSStyleDeclaration;}MDN ReferenceAPI reference →CSSStyleDeclaration.height: stringinterfaceCSSStyleDeclaration {height:string;}interfaceCSSStyleDeclaration {height:string;}MDN ReferenceAPI reference →GridOptions.workbook: WorkbookinterfaceGridOptions {workbook:Workbook;}interfaceGridOptions {workbook:Workbook;}Live workbook schema adopted by the store and updated by document operations.API reference →Workbook.activeSheet: stringinterfaceWorkbook {activeSheet:string;}interfaceWorkbook {activeSheet:string;}Active sheet ID and initial tab presented when the grid is created.API reference →Workbook.sheets: Sheet[]interfaceWorkbook {sheets:Sheet[];}interfaceWorkbook {sheets:Sheet[];}Sheets in display/tab order.API reference →Sheet.id: stringinterfaceSheet {id:string;}interfaceSheet {id:string;}Stable identifier, unique within the workbook and used by every cell address.API reference →Sheet.name: stringinterfaceSheet {name:string;}interfaceSheet {name:string;}User-facing sheet name shown in tabs and workbook exports.API reference →Sheet.rowCount: numberinterfaceSheet {rowCount:number;}interfaceSheet {rowCount:number;}Row count for both in-memory and datasource-backed sheets.API reference →Sheet.columns: Column[]interfaceSheet {columns:Column[];}interfaceSheet {columns:Column[];}Ordered schema; array positions are the zero-based column coordinates.API reference →Column.key: stringinterfaceColumn {key:string;}interfaceColumn {key:string;}Non-empty key, unique within the sheet, used to map input and datasource values.API reference →Column.header: stringinterfaceColumn {header:string;}interfaceColumn {header:string;}Schema label used by table exports and data-grid presentation headers.API reference →Column.type: CellFormatinterfaceColumn {type:CellFormat;}interfaceColumn {type:CellFormat;}Controls cell input parsing and default value formatting for this column.API reference →Column.width: numberinterfaceColumn {width:number;}interfaceColumn {width:number;}Unzoomed column width in CSS pixels.API reference →GridOptions.data?: ColumnarData | undefinedinterfaceGridOptions {data?:ColumnarData|undefined;}interfaceGridOptions {data?:ColumnarData|undefined;}Eager column-major values loaded into workbook.activeSheet; use instead of datasource.API reference →ColumnarData.rowCount: numberinterfaceColumnarData {rowCount:number;}interfaceColumnarData {rowCount:number;}Row count for both in-memory and datasource-backed sheets.API reference →ColumnarData.columns: Record<string, ArrayLike<CellScalar | CellValue>>interfaceColumnarData {columns:Record<string, ArrayLike<CellScalar|CellValue>>;}interfaceColumnarData {columns:Record<string,ArrayLike<CellScalar|CellValue>>;}Exact column runs represented by every returned row.API reference →product: string[]let product:string[]let product:string[]qty: number[]let qty:number[]let qty:number[]