Vanilla JS Grid Row Selection: TypeScript Patterns for Single & Multi Select (2026)

Vanilla TSTutorialRow Selection

Single, multi, and checkbox row selection for vanilla TypeScript data grids—strict TypeScript examples for simple-table-core and a comparison to Tabulator, Grid.js, and Handsontable.

For Vanilla JS / TypeScript developers building data grids in 2026.

Row selection drives bulk actions: delete, archive, export, assign. Get it wrong and users misclick or struggle on touch screens.

This tutorial walks through single, multi, and checkbox selection patterns for the vanilla JS / TS data grid landscape and shows the simple-table-core setup with strict TypeScript.

If you want a framework-agnostic, strict-TypeScript core grid that works in any host (web component, micro-frontend, plain HTML), simple-table-core is ~70 kB gzipped and MIT-licensed.

Why it matters

Bulk actions

Selection enables archive, delete, export, assign, etc. Without it, users repeat per-row actions.

Keyboard ergonomics

Shift-click range, Ctrl-click toggle, and Space-to-select are expected by power users.

Cross-page persistence

When users paginate, their selection should survive the navigation.

Accessibility

Screen readers and keyboard users need aria-selected and focus-visible states.

Vanilla JS / TypeScript library comparison

LibrarySupportNotes
simple-table-coreBuilt-in (single / multi / checkbox)selectableCells: 'row' + onRowSelect callback.
TabulatorBuilt-inselectable: true + selectableRangeMode + rowSelectionChanged event.
Grid.jsPluginSelection plugin; less polished than core grids.
HandsontableBuilt-in (commercial)Selection built-in but commercial license required.
jSpreadsheetSpreadsheet-styleCell-range selection; less suited for row-based UX.

Implementation: simple-table-core

Pass selectableCells: 'row' in options, listen for onRowSelect, and combine with sorting / filtering / pinning without extra config.

Use a stable identifier (database id, GUID) when tracking selection across pagination. Indexes break when filters or sorts change.

Common pitfalls

Tracking by index breaks

Problem: When users sort or filter, the same index points to a different row.

Solution: Always key selection by a stable identifier (id / uuid).

Tiny touch targets

Problem: Checkboxes are too small on phones, users miss-tap.

Solution: Provide at least 44x44px touch targets. simple-table-core's checkbox column does this.

Memory leaks on SPA navigation

Problem: Listeners persist after the host element is removed.

Solution: Call table.dispose() on cleanup so listeners are removed.

Lost selection on data refetch

Problem: After polling refresh, selection clears.

Solution: Reapply your stable-id Set after data refresh by reusing each row's existing id.

Frequently asked questions

Can I support Shift-click range selection?
Yes—simple-table-core handles range selection out of the box for selectableCells: 'row'.
Does it work in a web component?
Yes. Pass shadowRoot.querySelector('#host') as the element.
Is selection accessible?
Yes. simple-table-core sets aria-selected on rows and supports keyboard navigation (Space to toggle, arrow keys to move focus).

Wrap-up

Row selection in vanilla JS / TS is a single option and callback in simple-table-core. Tabulator and Handsontable also ship it but at larger bundle sizes (Handsontable is commercial).

Always key selection by stable identifier and provide proper touch targets. Combine with virtualization and pinning for large datasets.

Add row selection to your vanilla TS grid

simple-table-core ships single, multi, and checkbox selection in one MIT package—~70 kB gzipped, strict TypeScript, ESM-first.