Focus and virtualization

The arithmetic behind the grid's single tab stop and its virtualized rows.

Last updated November 3, 2026

nextFocusForKey works out where a navigation key moves the grid's tab stop, and visibleRange which rows a virtualized body draws. Both are plain arithmetic over numbers; @gridkitjs/dom's createGridFocus and createRowVirtualizer do the DOM side and call them for you. Reach for them directly in a renderer that keeps its own focus or scroll.

Focus#

One coordinate space covers the header and the body: a GridFocus is { rowIndex, columnIndex }, with body rows from 0 and the header at HEADER_ROW, -1, so arrowing up out of the first row reaches the header without a case of its own.

FunctionTypeDescription
nextFocusForKey(key, modifiers, focus, rowCount, columnCount, pageSize, isSkippableRow?)GridFocus | nullWhere key moves focus, or null when the key is not the grid's — any key but the arrows, Home, End, PageUp and PageDown, anything with Alt, and Ctrl with a left or right arrow, which resize and reorder.
clampFocus(focus, rowCount, columnCount)GridFocusfocus held inside the grid, so a tab stop left behind by a removed column or a shorter page still lands on a cell.

modifiers is a NavigationModifiers, { ctrlKey, metaKey, altKey, shiftKey } — a KeyboardEvent will do. pageSize is how many rows PageUp and PageDown move, measured once by the caller. Ctrl+Home and Ctrl+End go to the grid's first and last cells, plain Home and End to the row's.

isSkippableRow(rowIndex) names rows the vertical moves step over — a group's summary row under groupAggregateDisplay: "row", which holds a row's place but is no tab stop. A move with nowhere to land stays where it was. The left and right moves can run past the grid's edges, so pass the result through clampFocus:

import { clampFocus, nextFocusForKey } from "@gridkitjs/core";

const next = nextFocusForKey(
  event.key,
  event,
  focus,
  rowCount,
  columnCount,
  10,
);
if (next !== null) {
  focus = clampFocus(next, rowCount, columnCount);
  event.preventDefault();
}

Virtualization#

FunctionTypeDescription
visibleRange(heights, estimatedHeight, scrollTop, viewportHeight, overscan)VisibleRangeThe rows inside [scrollTop, scrollTop + viewportHeight), padded by overscan rows each side and kept within the rows there are. Rows need not be one height.

heights is a RowHeights, one entry per row: its measured height once known, undefined until then, which counts as estimatedHeight. A VisibleRange is what to draw and the space to leave around it:

FieldTypeDescription
startIndexnumberThe first row to draw.
endIndexnumberThe last row to draw, inclusive — -1 when there are no rows.
offsetBeforenumberThe height of every row before startIndex: the leading spacer.
offsetAfternumberThe height of every row after endIndex: the trailing spacer.

In the React grid#

See accessibility for the keys and row virtualization for virtualized.

Edit this page on GitHub