Capítulo 43 de 54
rowPinningFeature splits rows into top/center/bottom regions (mirroring column pinning's start/center/end), needs no row-model factory of its own, and by default keeps pinned rows visible even when filtering/pagination would otherwise remove them from view.
RowPinningState = { top: string[], bottom: string[] } (row ids). Default via initialState.rowPinning. Ownership: external atom (atoms: { rowPinning: atom }, v9-recommended) or classic state.rowPinning + onRowPinningChange.row.getCanPin(), row.getIsPinned() ('top' | 'bottom' | false), row.getPinnedIndex(), row.pin('top' | 'bottom' | false). row.pin() also accepts includeLeafRows/includeParentRows flags — relevant when pinning a grouped/expanded row and deciding whether its related parent or leaf rows should move along with it.table.getTopRows(), getCenterRows(), getBottomRows() — render these as three separate sections (or three separate <tbody>s) if pinned rows need visually distinct placement. table.getIsSomeRowsPinned() (optionally scoped to 'top'/'bottom'). table.setRowPinning({...}), table.resetRowPinning() / resetRowPinning(true) (clears both arrays).enableRowPinning on the table — boolean or a per-row predicate, e.g. (row) => row.original.status !== 'archived'.keepPinnedRows (default true): a pinned row stays visible in its region even if it would otherwise be filtered or paginated out of the center rows. Set false if pinned rows should only render when actually present in the current filtered+paginated row model — i.e. pinning becomes purely cosmetic reordering, not an "always visible" guarantee.const features = tableFeatures({ rowPinningFeature })
const table = useTable({ features, columns, data, initialState: { rowPinning: { top: ['0'], bottom: ['3'] } } })
<tbody>
{table.getTopRows().map((row) => <PinnedRow key={row.id} row={row} />)}
{table.getCenterRows().map((row) => <TableRow key={row.id} row={row} />)}
{table.getBottomRows().map((row) => <PinnedRow key={row.id} row={row} />)}
</tbody>
// pin controls
row.getCanPin() && (
<>
<button onClick={() => row.pin('top')} disabled={row.getIsPinned() === 'top'}>Top</button>
<button onClick={() => row.pin(false)} disabled={!row.getIsPinned()}>Center</button>
</>
)
getCanPin/getIsPinned/pin.keepPinnedRows: true (default) is what makes pinning behave like "always visible" rather than just reordering — decide deliberately if you want the opposite.includeLeafRows/includeParentRows on row.pin() when pinning interacts with grouped/expanded rows, to control whether relatives move together.