Tablecraft
API Reference

useColumnResizing

Drag-to-resize handle props for table headers, pairing with useTable's columnResizing option.

Drag-to-resize handle props for table headers. Spread the returned props onto a resize handle element and the column commits a new width as the user drags.

Split from useTable on purpose: the committed widths are table state and belong in useTable's return, but building the drag handle needs the table instance itself. That is the same seam useTableA11y and useVirtualRows sit on.

Signature

function useColumnResizing<TData extends RowData>(
  table: TablecraftTable<TData>
): ColumnResizingReturn

Pairs with useTable({ columnResizing: true })

useColumnResizing only builds handle props from the table instance — it does not itself register where a committed width gets written. TanStack's columnSizing slice ships its own internal state updater regardless, so without columnResizing enabled on useTable (or useQueryTable/useServerTable) the slice is merely uncontrolled rather than absent: dragging a handle still visibly resizes the column, but the width never reaches useTable().columnResizing.state (it stays {}), and columnResizing.setSize(...) updates that hook's own state without changing the rendered width. Both halves silently disagree with each other — which is exactly why you want the option on.

import { useTable, useColumnResizing } from '@marvinackerman/tablecraft'

const { table, columnResizing } = useTable({
  data,
  columns,
  columnResizing: true,
})

const { getResizeHandleProps } = useColumnResizing(table)

columnResizing on useTable's return is the committed-width slice (ColumnSizingReturn) — state, setSize, resetSize, resetAll, getSize. See useTable and useColumnSizingState for that half.

Return

PropertyTypeDescription
getResizeHandleProps(headerId: string) => ResizeHandlePropsProp-getter for a header's resize handle
isResizing(columnId: string) => booleanWhether that column is currently being dragged
resizingColumnIdstring | nullThe column currently being resized, if any

ResizeHandleProps

PropertyTypeDescription
onMouseDown(event) => voidOnly present when the column can resize
onTouchStart(event) => voidOnly present when the column can resize
style{ cursor, touchAction: 'none', userSelect: 'none' }cursor is 'col-resize' when resizable, 'default' otherwise
data-can-resizebooleanStyling hook — see below
data-resizingbooleanStyling hook — see below

Usage

{table.getFlatHeaders().map((header) => (
  <th key={header.id} style={{ width: header.getSize() }}>
    {flexRender(header.column.columnDef.header, header.getContext())}
    <div
      {...getResizeHandleProps(header.id)}
      className="resize-handle"
    />
  </th>
))}

data-can-resize and data-resizing as styling hooks

Both are plain data-* attributes on the returned props, meant for CSS rather than for branching in your component:

.resize-handle[data-can-resize='false'] {
  display: none;
}
.resize-handle[data-resizing='true'] {
  background: var(--accent);
}

For an unknown header id or a column that opted out of resizing, getResizeHandleProps still returns a valid object — data-can-resize: false, no onMouseDown/onTouchStart — rather than throwing. A stale header id in a consumer's render can't crash the table.

TanStack's table-level enableColumnResizing: false is not reachable through tablecraft — useTable builds an explicit options object rather than spreading unknown options through, and ColumnResizingOptions does not expose it. The supported opt-out is per-column: set enableResizing: false on that column's definition, as below.

enableResizing: false yields inert props

Setting enableResizing: false on a column def makes header.column.getCanResize() return false for that column, which is exactly the "unknown header" case above: getResizeHandleProps returns inert props with data-can-resize: false and no event handlers. No separate check is needed on the consumer side — the same props object works whether or not the column can resize.

The 'onEnd' default

columnResizing: { mode } on useTable — see ColumnResizingOptions — defaults to 'onEnd', preserving TanStack's own default rather than diverging from it.

'onChange' commits a new width to columnSizing state on every mousemove during the drag, which re-renders the whole table on every pixel of movement. tablecraft ships virtualization for large tables specifically because full-table re-renders are expensive; committing that often during a resize is visibly janky on exactly the tables where it matters most. 'onEnd' commits once, when the drag ends: header.getSize() reads from the committed columnSizing map either way, so with 'onEnd' the column's own width does not change mid-drag — only the cursor moves — and the new width appears in one step on mouseup/touchend, rather than growing pixel-by-pixel.

To opt into TanStack's live-committing behavior:

useTable({
  data,
  columns,
  columnResizing: { mode: 'onChange' },
})

On this page