Maintain rowSelection ID state with stable getRowId, single, multi, subrow, and Shift-range rules, selected row models, handler anchors, and manual-pagination semantics. Load when implementing getToggleSelectedHandler, enableRowRangeSelection, selectChildren, deselectParents, or selected IDs that outlive loaded Row objects.
63
76%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./packages/table-core/skills/row-selection/SKILL.mdThis skill builds on core and table-features. Selection is independent ID state; selected row models can only materialize loaded rows.
import {
rowSelectionFeature,
tableFeatures,
type Row,
} from '@tanstack/table-core'
type Person = { id: string; name: string }
export const features = tableFeatures({ rowSelectionFeature })
export const options = {
getRowId: (row: Person) => row.id,
enableSubRowSelection: false,
}const selectedIds = table.getSelectedRowIds()
const loadedSelectedRows = table.getSelectedRowModel().rowsUse IDs for database-wide intent and row models for currently loaded objects.
export function getSelectionHandler(row: Row<typeof features, Person>) {
return row.getToggleSelectedHandler()
}The handler establishes a table-local anchor on ordinary interactions and applies the checked value to the inclusive current display-order range on Shift interactions. Range behavior is enabled by default; set enableRowRangeSelection: false to preserve non-range handler behavior. Direct row.toggleSelected() and table.setRowSelection() calls do not move that anchor.
Pass the original checkbox click event to this handler. DOM change events often omit modifier keys, so use the framework's click binding for row checkboxes unless its change event exposes the original click through nativeEvent (as React does).
export function getDisplayedRowsOnlyHandler(row: Row<typeof features, Person>) {
return row.getToggleSelectedHandler({ selectChildren: false })
}The default selectChildren: true recursively changes selectable descendants of parents encountered in the range. Set it to false when collapsed descendants outside the display-order interval must remain unchanged.
export function getPruningHandler(row: Row<typeof features, Person>) {
return row.getToggleSelectedHandler({ deselectParents: true })
}Selecting a parent cascades its id plus selectable descendant ids into state, but deselecting a child later leaves the parent id behind by default (some tables treat state ids as literal selections, e.g. with selectChildren: false). The default deselectParents: false preserves that; set it to true so deselecting any row also deletes every ancestor id, keeping row.getIsSelected() honest for parents. Applies to toggleSelected and both plain and Shift-range handler paths.
With enableSubRowSelection: false (or a per-row predicate), table.toggleAllRowsSelected() skips descendants of blocking parents, and getIsAllRowsSelected()/getIsAllPageRowsSelected() exclude those descendants from the all-selected computation, so the header checkbox still reads checked. Deselect-all skips rows whose enableRowSelection resolves false, preserving their selection; use toggleAllRowsSelected(false, { deselectAll: true }) or resetRowSelection(true) to clear everything including disabled and out-of-model ids.
Wrong: data = data.filter(row => row.id !== deletedId)
Correct: data = data.filter(row => row.id !== deletedId); table.setRowSelection(old => { const next = { ...old }; delete next[deletedId]; return next })
Selection is independent state and can retain IDs after data removal.
Source: https://github.com/TanStack/table/issues/5850
Wrong: const options = { getRowId: (_row: Person, index: number) => String(index) }
Correct: const options = { getRowId: (row: Person) => row.id }
Stable application IDs preserve identity as row order and pages change.
Source: docs/framework/react/guide/row-selection.md#useful-row-ids
Wrong: const allSelectedRecords = table.getSelectedRowModel().rows
Correct: const allSelectedIds = table.getSelectedRowIds()
Under manual pagination, unloaded selected IDs have no Row object in the current model.
Source: docs/framework/react/guide/row-selection.md#note-if-you-are-using-manualpagination
Wrong:
const onChange = (event: { target: { checked: boolean } }) =>
row.toggleSelected(event.target.checked)Correct:
const onChange = row.getToggleSelectedHandler()Only successful interactions through getToggleSelectedHandler() establish or advance the Shift-range anchor. The handler also supports custom range-event detection through isRowRangeSelectionEvent.
Source: docs/framework/react/guide/row-selection.md#shift-range-selection
Wrong:
checkbox.addEventListener('change', row.getToggleSelectedHandler())Correct:
checkbox.addEventListener('click', row.getToggleSelectedHandler())The range modifier must be present on the event passed to the handler. Raw DOM change events do not reliably expose click modifier keys.
Source: docs/framework/svelte/guide/row-selection.md#shift-range-selection
Wrong:
const options = {
manualPagination: true,
enableRowRangeSelection: true,
}
// Shift cannot select rows absent from data.Correct:
const options = {
manualPagination: true,
getRowId: (row: Person) => row.id,
}Client-side ranges can cross pages because display order is pre-pagination. Manual/server pagination cannot include rows absent from the loaded data; select database-wide IDs in application state when that behavior is required.
Source: docs/framework/react/guide/row-selection.md#shift-range-selection, https://github.com/TanStack/table/issues/4781
Inspect node_modules/@tanstack/table-core/dist/features/row-selection/ for state, row-model variants, and selection enablement callbacks.
9e523bc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.