Tablecraft
API Reference

useTable

The primary hook — covers pagination, sorting, filtering, selection, expansion, grouping, pinning, persistence, and URL sync in one call.

The single hook that covers most use cases. Every option is optional; the returned table is a full TanStack Table instance with every feature bound.

Because TanStack Table v9 types a table as Table<TFeatures, TData>, tablecraft exports the alias TablecraftTable<TData> — the instance type with tablecraft's feature set already filled in. Use it wherever you need to name the type:

import type { TablecraftTable } from '@marvinackerman/tablecraft'

function Toolbar({ table }: { table: TablecraftTable<User> }) { /* … */ }

Signature

function useTable<TData extends RowData>(
  options: UseTableOptions<TData>
): UseTableReturn<TData>

Options

OptionTypeDescription
dataTData[]Row data (required)
columnsColumnDef<TablecraftFeatures, TData, any>[]Column definitions (required)
paginationPaginationOptions | boolean{ pageSize?, pageIndex? }, or true (defaults), or false (disabled)
manualPaginationbooleanServer-driven pagination — pair with rowCount
rowCountnumberTotal row count, used with manualPagination
onPaginationChangeOnChangeFn<PaginationState>Custom pagination change handler
sortingSortingOptions | boolean{ defaultSort? }, or true, or false
manualSortingbooleanServer-driven sorting
onSortingChangeOnChangeFn<SortingState>Custom sorting change handler
globalFilterbooleanEnable global text filter
onGlobalFilterChange(value: string) => voidCustom global filter handler
columnFiltersbooleanEnable per-column filters
onColumnFiltersChangeOnChangeFn<ColumnFiltersState>Custom column filter handler
rowSelectionRowSelectionOptions | booleantrue, or { defaultSelection?: RowSelectionState, enableMultiRowSelection? }
columnVisibilityColumnVisibilityOptions | booleantrue, or { defaultVisibility? }
rowExpansionRowExpansionOptions | booleantrue, or { defaultExpanded?, allowMultiple?, paginateExpandedRows?, getSubRows? }
groupingGroupingOptions | booleantrue, or { defaultGrouping?, manualGrouping?, groupedColumnMode? }
columnPinningColumnPinningOptions | booleantrue, or { defaultPinning? }
columnOrderColumnOrderOptions | booleantrue, or { defaultOrder? }
rowPinningRowPinningOptions | booleantrue, or { defaultPinning? }
columnResizingColumnResizingOptions | booleantrue, or { defaultSizing?, mode?, direction? } — mode defaults to 'onEnd'
fuzzyboolean | FilterFn<TablecraftFeatures, TData>true loads match-sorter via require() — CJS/Node only. In ESM-only bundlers (Vite, browsers) import fuzzyFilterFn from @marvinackerman/tablecraft/fuzzy and pass that — see below.
persist'localStorage' | 'sessionStorage' | falsePersist state across reloads — see Persistence & URL sync
persistKeystringStorage key — required when persist is set
persistOptionsPersistOptionsWhich state slices to persist
syncUrlboolean | URLSyncOptionsSync state to the URL — see Persistence & URL sync

Typo-tolerant search in Vite and other ESM bundlers

fuzzy: true reaches match-sorter through require(), which throws in ESM-only bundlers. The /fuzzy entry imports it the normal way, so it works everywhere — and consumers who never use fuzzy search do not carry the code:

import { useTable } from '@marvinackerman/tablecraft'
import { fuzzyFilterFn } from '@marvinackerman/tablecraft/fuzzy'

useTable({ data, columns, globalFilter: true, fuzzy: fuzzyFilterFn })

match-sorter is an optional peer: install it only if you use this.

Usage

import { useTable } from '@marvinackerman/tablecraft'

const {
  table,          // Full TanStack table instance — TablecraftTable<TData>
  pagination,
  sorting,
  globalFilter,
  columnFilters,
  rowSelection,
  columnVisibility,
  rowExpansion,
  grouping,
  columnPinning,
  columnOrder,
  rowPinning,
  columnResizing,
  emptyState,
} = useTable({
  data,
  columns,
  pagination: { pageSize: 20 },
  sorting: { defaultSort: [{ id: 'name', desc: false }] },
  globalFilter: true,
  columnFilters: true,
  rowSelection: true,
  columnVisibility: { defaultVisibility: { email: false } },
  rowExpansion: { getSubRows: (row) => row.children },
  grouping: { defaultGrouping: ['role'] },
  columnOrder: true,
  rowPinning: true,
  columnResizing: true,
  fuzzy: true,
  persist: 'localStorage',
  persistKey: 'my-table',
  persistOptions: { sorting: true, pagination: true },
  syncUrl: true,
})

Return

pagination

PropertyTypeDescription
pageIndexnumberCurrent page (0-indexed)
pageSizenumberRows per page
pageCountnumberTotal pages
canPreviousPagebooleanCan go back
canNextPagebooleanCan go forward
previousPage() => voidGo to previous page
nextPage() => voidGo to next page
setPageIndex(index: number) => voidJump to page
setPageSize(size: number) => voidChange page size

sorting

PropertyTypeDescription
sortingStateSortingStateCurrent sort state
setSortingOnChangeFn<SortingState>Set sort state directly
clearSorting() => voidClear all sorting

globalFilter

PropertyTypeDescription
valuestringCurrent search string
setValue(val: string) => voidSet the search string
clear() => voidClear the search string

columnFilters

PropertyTypeDescription
stateColumnFiltersStateCurrent per-column filter values
setFilter(columnId, value) => voidSet a single column's filter
clearFilter(columnId) => voidClear one column's filter
clearAll() => voidClear all column filters

rowSelection

PropertyTypeDescription
stateRowSelectionStateCurrent selection map
toggleRow(rowId: string) => voidToggle a row
toggleAll() => voidSelect / deselect all
clearSelection() => voidClear all
selectedRowIdsstring[]IDs of selected rows
selectedCountnumberNumber selected
isSelected(rowId: string) => booleanCheck if selected

columnVisibility

PropertyTypeDescription
stateColumnVisibilityStateCurrent visibility map
toggleColumn(columnId: string) => voidToggle a column
showColumn(columnId: string) => voidShow a column
hideColumn(columnId: string) => voidHide a column
showAll() => voidShow every column
hiddenColumnsstring[]Currently hidden column IDs

rowExpansion

PropertyTypeDescription
stateExpandedStateCurrent expanded rows
toggleRow(rowId: string) => voidToggle expand
expandRow(rowId: string) => voidExpand a row
collapseRow(rowId: string) => voidCollapse a row
clearExpansion() => voidCollapse all
expandedRowIdsstring[]IDs of expanded rows
isExpanded(rowId: string) => booleanCheck if expanded

grouping

PropertyTypeDescription
stateGroupingStateCurrent grouped columns
toggleGrouping(columnId: string) => voidToggle group by column
setGrouping(cols: GroupingState) => voidSet grouping directly
clearGrouping() => voidRemove all grouping
isGrouped(columnId: string) => booleanCheck if grouped
groupedColumnsstring[]Currently grouped column IDs

columnPinning

PropertyTypeDescription
stateColumnPinningStateRaw TanStack pinning state
pinStart(columnId: string) => voidPin column to the start edge (left in LTR, right in RTL)
pinEnd(columnId: string) => voidPin column to the end edge (right in LTR, left in RTL)
unpin(columnId: string) => voidRemove pin from column
clearPinning() => voidUnpin all columns
isPinned(columnId: string) => 'start' | 'end' | falseQuery pin status
startColumnsstring[]Currently start-pinned column IDs
endColumnsstring[]Currently end-pinned column IDs

columnOrder

PropertyTypeDescription
stateColumnOrderStateCurrent column order, as leaf column ids
setOrder(columnIds: string[]) => voidReplace the order wholesale
moveColumn(columnId: string, toIndex: number) => voidMove one column to a new index
resetOrder() => voidClear back to [] (TanStack's natural order)
orderstring[]Alias of state

An empty order (the default) is TanStack's sentinel for natural order, so moveColumn is a no-op until you seed columnOrder: { defaultOrder: [...] } — e.g. table.getAllLeafColumns().map(c => c.id). No drag-and-drop UI is provided; wire moveColumn to a drag library like dnd-kit or to plain buttons — see useColumnOrderState for the reasoning.

rowPinning

PropertyTypeDescription
stateRowPinningStateRaw TanStack row pinning state
pinTop(rowId: string) => voidPin a row to the top
pinBottom(rowId: string) => voidPin a row to the bottom
unpin(rowId: string) => voidRemove pin from a row
clearPinning() => voidUnpin all rows
isPinned(rowId: string) => 'top' | 'bottom' | falseQuery pin status
topRowsstring[]Currently top-pinned row IDs
bottomRowsstring[]Currently bottom-pinned row IDs

Rows pin to top/bottom, unlike columnPinning's start/end — see useRowPinningState for why.

columnResizing

PropertyTypeDescription
stateColumnSizingStateCommitted widths, keyed by leaf column id
setSize(columnId: string, px: number) => voidSet a column's width directly
resetSize(columnId: string) => voidRemove a column's override, falling back to its columnDef size
resetAll() => voidClear all overrides
getSize(columnId: string) => number | undefinedCommitted width, or undefined if unset

This slice is the committed widths only — the state half of resizing. Its type is ColumnSizingReturn, not ColumnResizingReturn — the two names are easy to conflate. ColumnResizingReturn is the return type of the separate hook, useColumnResizing(table), which builds the drag handles that write into this slice; it needs the table instance rather than options and so isn't returned from useTable itself. If you hit a TS2322 between these two near-identical names, useTable().columnResizing is ColumnSizingReturn and useColumnResizing(table)'s return is ColumnResizingReturn.

emptyState

PropertyTypeDescription
isEmptybooleantrue when data has 0 rows total
isFilteredEmptybooleantrue when data exists but filters return 0 rows

Notes

  • position: sticky for pinned columns is plain CSS — tablecraft only provides state and the pixel offsets (column.getStart('start'), column.getAfter('end')). No UI lock-in.
  • columnPinning is also available on useQueryTable and useInfiniteTable with the identical option shape.
  • columnOrder, rowPinning and columnResizing are available on useServerTable (it delegates straight to useTable) and on useQueryTable (its return type extends useTable's) with the identical option shape. useInfiniteTable does not get any of the three — its return type is standalone rather than extending useTable's, so nothing forced these three onto it, and they were left out of scope deliberately rather than by oversight.
  • See Persistence & URL sync for persist* / syncUrl details, and State hooks if you're composing useReactTable yourself instead of using useTable.

On this page