large-list-data-grid-contracts

Installation
SKILL.md

Large list data grid contracts

A virtualized list or grid keeps only a window of rows mounted, so three things form a contract: the total scroll size derived from a per-row size estimate, the overscan buffer that mounts rows just outside the viewport, and the set of features that assume every row is in the DOM (find-in-page, screen-reader counts, focus, sticky alignment). The bugs are never in the small happy-path fixture; they surface at the seams — a wrong estimate yanks the scroll, too-little overscan blanks white, and DOM-window assumptions fail silently on real data.

Checklist (lead with the trap)

  1. A test that disables virtualization hides every bug below it. disableVirtualization (MUI X), onShouldVirtualize={() => false} (Fluent), renderAllRows (Handsontable), or a 5-row jsdom fixture mounts the whole list, so blank rows, offset drift, and focus loss never reproduce. Keep at least one path with virtualization ON — a browser/e2e test at a realistic item count and a fixed container height — or CI stays green on a broken grid.
  2. estimateSize must be close, and measurement must not yank the scroll. The virtualizer sizes the total scroll range from estimateSize before rows measure; when measureElement reports the real height, following offsets shift. A flat () => 35 for rows that wrap to variable heights makes scrolling jump and flicker as each row corrects. Give a realistic estimate and attach measureElement so measured rows report true size.
  3. Prepending items (chat/feed) is where position-loss bites hardest. Inserting rows above the viewport pushes every following offset down; with no compensation the user's read position jumps. Anchor to a stable item (many libs expose firstItemIndex/prepend support or a scrollToIndex restore). Note CSS overflow-anchor only anchors real DOM nodes — it cannot anchor a row the virtualizer has not mounted.
  4. Overscan is a two-sided budget. Too small blanks white gaps on fast scroll (rows unmount before the next ones mount and measure); too large mounts extra rows every frame and costs INP and memory. Tune per surface, and lean slightly higher for dynamically-measured lists where measurement lag is what causes the blanks.
  5. Features that assume rows are all in the DOM break silently. Ctrl+F / find-in-page matches nothing off-screen; "select all", export-visible, and querySelector-based logic see only the window. If in-page search is a requirement, content-visibility: auto with contain-intrinsic-size keeps rows in the DOM (searchable, in the a11y tree) while skipping paint — a different tradeoff from true windowing, not a drop-in fix.
  6. Screen readers need the full set size, not the mounted count. Without aria-setsize/aria-posinset on list items (role="option"/listitem, or article in a role="feed"), AT announces "3 of 20" instead of "3 of 5000". For grids, put aria-rowcount/aria-colcount on the role="grid" container and aria-rowindex/aria-colindex reflecting the true position on partially-loaded rows and cells; use -1 when the total is unknown.
  7. Focus is lost when the focused row unmounts. Scrolling the focused row out of the window removes its node, so focus falls back to <body> — keyboard nav and the AT reading position reset. Manage focus deliberately (roving tabindex, restore on remount); the grid pattern expects author-managed focus with a single tab stop into the widget.
  8. Sticky header and pinned column must share the body's scroll math. A header or pinned column rendered outside the virtualized body drifts from the rows when heights are dynamic or the scrollbar changes width. Verify alignment with variable-height rows and at the top and bottom scroll extremes, not just at rest.

Quick probes

Treat hits as leads; confirm the estimate-to-measure-to-render path and the aria/focus wiring at the call site. No linter catches windowing bugs — the source of truth is a browser/e2e scroll test with virtualization enabled; route the aria row/set-size assertions to a11y-contract-testing's harness (axe/AT), since value-dependent attributes are invisible to eslint-plugin-jsx-a11y.

Installs
1
GitHub Stars
1
First Seen
3 days ago
large-list-data-grid-contracts — voidmatcha/frontend-niche-skills