web-ui-tanstack-table-d9ec88 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-ui-tanstack-table-d9ec88 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: TanStack Table is a headless UI library for building powerful tables and datagrids. UseuseReactTablehook withcreateColumnHelperfor type-safe column definitions. Import only the row models you need (getSortedRowModel,getFilteredRowModel, etc.) for tree-shaking. Memoize data and columns withuseMemoto prevent infinite re-renders. SetmanualPagination,manualSorting,manualFilteringtotruefor server-side data.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)(You MUST memoize data and columns with `useMemo` - unstable references cause infinite re-renders)
(You MUST use `createColumnHelper<TData>()` for type-safe column definitions with proper TValue inference)
(You MUST import row models explicitly - `getSortedRowModel`, `getFilteredRowModel`, etc. - for tree-shaking)
(You MUST use `accessorKey` for direct property access and `accessorFn` with explicit `id` for computed values)
(You MUST set `manualPagination`, `manualSorting`, `manualFiltering` to `true` for server-side data)
</critical_requirements>
Auto-detection: TanStack Table, @tanstack/react-table, useReactTable, createColumnHelper, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, ColumnDef, column definitions, table state
When to use:
When NOT to use:
Key patterns covered:
Detailed Resources:
<philosophy>
TanStack Table is a headless UI library - it provides the logic for tables without any markup or styles. This gives you complete control over rendering while the library handles complex state management for sorting, filtering, pagination, and more.
Core Principles:
Why Headless?
The headless approach means TanStack Table handles the hard parts (state management, sorting algorithms, pagination logic) while you control presentation. This is ideal when:
</philosophy>
<patterns>
Set up a type-safe table with useReactTable and createColumnHelper. See examples/core.md for complete implementation.
const columnHelper = createColumnHelper<User>();
const columns = useMemo(
() => [
columnHelper.accessor("firstName", { header: "First Name" }),
// accessorFn for computed values - MUST include id
columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
id: "fullName",
header: "Full Name",
}),
],
[],
);
const data = useMemo(() => users, [users]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getRowId: (row) => row.id,
});Critical: Memoize both columns and data - unstable references cause infinite re-renders.
Enable sorting with getSortedRowModel and controlled state. See examples/sorting.md.
const [sorting, setSorting] = useState<SortingState>([]);
const table = useReactTable({
data,
columns,
state: { sorting },
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});
// In column def:
columnHelper.accessor("createdAt", {
header: "Created",
sortingFn: "datetime", // Required for Date objects
});Gotcha: Dates don't sort correctly with default sort. Use sortingFn: "datetime" for Date columns.
Column filters and global filter with getFilteredRowModel. See examples/filtering.md.
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [globalFilter, setGlobalFilter] = useState("");
const table = useReactTable({
data,
columns,
state: { columnFilters, globalFilter },
onColumnFiltersChange: setColumnFilters,
onGlobalFilterChange: setGlobalFilter,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
});Gotcha: Multiple column filters combine with AND logic, not OR. Use global filter or custom logic for OR behavior.
Client-side and server-side pagination with getPaginationRowModel. See examples/pagination.md.
const DEFAULT_PAGE_SIZE = 10;
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: DEFAULT_PAGE_SIZE,
});
const table = useReactTable({
data,
columns,
state: { pagination },
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});Gotcha: pageIndex is 0-based internally, but many APIs are 1-based. Add 1 when sending to server.
Single and multi-row selection. See examples/selection.md.
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const table = useReactTable({
data,
columns,
state: { rowSelection },
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
enableRowSelection: true,
getRowId: (row) => row.id, // CRITICAL: Stable IDs for selection
});Critical: Without getRowId, selection uses array indices which break when data is re-ordered or filtered.
Handle server-side pagination, sorting, and filtering. See examples/server-side.md.
const table = useReactTable({
data: apiData ?? [],
columns,
state: { pagination, sorting, columnFilters },
onPaginationChange: setPagination,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
// CRITICAL: All three manual flags for server-side
manualPagination: true,
manualSorting: true,
manualFiltering: true,
rowCount: totalFromApi,
});Critical: Do NOT import client-side row models (getSortedRowModel, etc.) with manual*: true - they are redundant.
Toggle column visibility. See examples/column-visibility.md.
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({
email: false, // Hide by default
});
// In column def - prevent hiding required columns:
columnHelper.accessor("id", { enableHiding: false });Expandable rows for hierarchical data or detail views. See examples/expanding.md.
const [expanded, setExpanded] = useState<ExpandedState>({});
const table = useReactTable({
data,
columns,
state: { expanded },
onExpandedChange: setExpanded,
getCoreRowModel: getCoreRowModel(),
getExpandedRowModel: getExpandedRowModel(),
getRowCanExpand: () => true,
});Leverage TypeScript generics for a reusable table component. See examples/core.md.
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
// ... render table
}Keep columns visible during horizontal scroll. See examples/column-pinning.md.
const [columnPinning, setColumnPinning] = useState<ColumnPinningState>({
left: ["id"],
right: ["actions"],
});Critical: Pinning provides state only. You must apply position: sticky and background CSS yourself to prevent content overlap.
User-adjustable column widths. See examples/column-resizing.md.
const table = useReactTable({
data,
columns,
columnResizeMode: "onChange", // or "onEnd" for simpler, more performant
enableColumnResizing: true,
});Critical: columnResizeMode: "onChange" requires CSS variables pattern and memoized table body for 60fps performance. Use "onEnd" for simpler cases.
</patterns>
<red_flags>
High Priority Issues:
accessorFn without providing an id causes runtime errors.manualPagination: true when using server-side data causes the table to paginate already-paginated data.cell option for JSX rendering.Medium Priority Issues:
rowCount or pageCount, the table cannot calculate correct page count.getRowId, row selection uses array indices which break on sort/filter.Gotchas & Edge Cases:
sortingFn: "datetime" - JavaScript dates don't sort correctly by defaultpageIndex is 0-based - Many APIs use 1-based; add 1 when sending to serverautoResetPageIndex defaults to true - Page resets to 0 when data changes; set to false for server-sidecolumnResizeMode: "onChange" needs CSS variables + memoized body for performancegetResizeHandler to both onMouseDown and onTouchStart for mobile support</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST memoize data and columns with `useMemo` - unstable references cause infinite re-renders)
(You MUST use `createColumnHelper<TData>()` for type-safe column definitions with proper TValue inference)
(You MUST import row models explicitly - `getSortedRowModel`, `getFilteredRowModel`, etc. - for tree-shaking)
(You MUST use `accessorKey` for direct property access and `accessorFn` with explicit `id` for computed values)
(You MUST set `manualPagination`, `manualSorting`, `manualFiltering` to `true` for server-side data)
Failure to follow these rules will cause infinite re-renders, TypeScript errors, and incorrect server-side behavior.
</critical_reminders>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.