Features Documentation
In-depth reference for every interactive feature built into TableCraft. Each section covers how the feature works internally, which config flag controls it, and any edge cases or server-side considerations to be aware of.
Sorting
Sorting allows users to order table rows by the values in any data column. The feature is enabled by default and requires no additional configuration beyond using DataTableColumnHeader in your column definitions.
How it works
DataTableColumnHeaderrenders a dropdown button in each column header. The dropdown presents Sort Ascending and Sort Descending options, plus a Hide Column entry.- Selecting a sort option calls
column.toggleSorting(desc)on the TanStack Table instance, which updates the internalSortingState— an array of{ id, desc }objects. - The URL is updated to reflect the active sort:
?sort=columnId.ascor?sort=columnId.desc. On mount, the component reads this param to restore the sorted state after navigation or page refresh. - Visual indicators update on the header button: an
ArrowUpicon for ascending, anArrowDownicon for descending, and a neutralSortAscicon when the column is not currently sorted.
Config flag
Set features.sorting: false in the provider or instance config to disable sorting globally. This sets enableSorting: false on the underlying TanStack Table instance, preventing all columns from being sorted.
Per-column control
To disable sorting for a specific column without touching the global flag, set enableSorting: false directly on the ColumnDef. That column will render a plain (non-interactive) header instead of a DataTableColumnHeader button, or the dropdown will omit the sort options if you still use DataTableColumnHeader.
isQuery* flag is true, the table switches to manualSorting: true and will not reorder data locally — it expects pre-sorted data from the server on each fetch.Filtering
TableCraft provides three filtering modes: client-side faceted filters that operate on in-memory data, server-side filters that delegate to a callback, and an advanced filter builder for complex multi-rule queries.
Client-Side Filtering
Client-side filtering operates entirely in the browser using TanStack Table's getFilteredRowModel(). Pass a filterableColumns array to enable it:
- Faceted multi-select (
DataTableFacetedFilter) — renders a command-palette popover with a search input and checkboxes. Multiple values can be selected simultaneously. Selected values are written tocolumn.setFilterValue(array)and the URL is updated as?columnId=value1.value2(dot-separated). Active filters are displayed as dismissible badge chips in the toolbar's second row. - Single-select (
DataTableSingleSelectFilter) — radio behavior with an "All" option that clears the filter. SetisSingleSelect: trueon theDataTableFilterableColumnentry to use this variant. The popover closes automatically on selection.
Active filters can be cleared individually by clicking the badge's dismiss button, or all at once via the Reset button that appears in the filter row whenever at least one filter is active.
Server-Side Filtering
Enable server-side filtering with isQueryFilter={true}. In this mode the table instance is created with manualFiltering: true so no client-side row filtering is applied. Instead, each filter interaction calls your handleFilterChange(key, value) callback, which you use to update your query parameters and trigger a new data fetch.
<ClientSideTable
data={data}
columns={columns}
pageCount={pageCount}
isQueryFilter={true}
handleFilterChange={(key, value) => {
setFilters(prev => ({ ...prev, [key]: value }))
}}
filterableQuery={[
{ id: "status", title: "Status", options: [
{ label: "Active", value: "active" },
{ label: "Inactive", value: "inactive" },
]},
]}
currentFilters={filters}
onClearFilters={() => setFilters({})}
/>Use currentFilters to pass the current filter state back into the toolbar so the active filter chips render correctly. Call onClearFilters to reset all filter state when the user clicks the toolbar Reset button.
Advanced Filtering
The advanced filter builder provides a per-column rule editor with operators and multi-rule logic. Enable it with isShowAdvancedFilter={true} or by setting features.advancedFilter: true in config. When active, the standard toolbar is replaced by DataTableAdvancedToolbar.
- Operators — each filter rule supports
contains,is,is not, anddoes not containvarieties, selectable via a dropdown on each filter pill (DataTableAdvancedFilterItem). - AND / OR logic —
DataTableMultiFilterlets users add, duplicate, and remove multiple rules for the same column, and toggle the logical operator between AND and OR. - URL format —
?columnId=value.variety, for example?name=john.contains.
Pagination
The DataTablePagination footer renders page navigation, a page-size selector, and a record count summary. It adapts to a compact page / total format on mobile and uses a smart ellipsis algorithm on desktop to avoid rendering excessive page buttons.
Client-Side Pagination
By default, TableCraft uses TanStack Table's getPaginationRowModel() to split your static data array into pages in the browser. No server interaction is required.
- Config options:
pagination.defaultPageSize(default10) sets the initial rows-per-page value.pagination.pageSizeOptions(default[10, 20, 30, 40, 50]) populates the page-size dropdown. - URL sync: Current page and page size are written to
?page=1&per_page=10on every navigation so the user can share or bookmark a specific page view.
Server-Side Pagination
Enable server-side pagination with isQueryPagination={true}. The table renders pagination controls driven entirely by the metadata returned from your API. Page navigation and page-size changes invoke your callbacks rather than mutating internal state.
<ClientSideTable
data={data}
columns={columns}
pageCount={meta.last_page}
isQueryPagination={true}
paginationData={{
paginationResponse: {
meta: {
current_page: meta.current_page,
last_page: meta.last_page,
per_page: meta.per_page,
total: meta.total,
},
},
onPageChange: (page) => fetchData({ page }),
onPageSizeChange: (size) => fetchData({ per_page: size }),
}}
/>The paginationResponse.meta object must provide current_page, last_page, per_page, and total. These values match the standard Laravel paginator response shape. The onPageChange(page) callback receives the 1-based page number; the onPageSizeChange(size) callback receives the new page size integer.
Cursor-Based Pagination
For APIs that use opaque cursors instead of page numbers (GraphQL Relay, Stripe, keyset pagination), enable cursor mode with isCursorPagination={true}. In this mode the pagination footer renders only Previous and Next buttons — no numbered page buttons or first/last jumps — because cursor-based APIs do not support random page access.
import type { CursorPaginationData } from "react-table-craft"
const cursorPaginationData: CursorPaginationData = {
pageInfo: {
hasNextPage: pageInfo.hasNextPage,
hasPreviousPage: pageInfo.hasPreviousPage,
startCursor: pageInfo.startCursor,
endCursor: pageInfo.endCursor,
totalCount: pageInfo.totalCount, // optional
},
onNextPage: () => fetchMore({ after: pageInfo.endCursor }),
onPreviousPage: () => fetchMore({ before: pageInfo.startCursor }),
onPageSizeChange: (size) => fetchMore({ first: size }),
pageSize: 20,
}
<ClientSideTable
data={edges.map(e => e.node)}
columns={columns}
isCursorPagination={true}
cursorPaginationData={cursorPaginationData}
/>The cursorPaginationData object provides a pageInfo shape with hasNextPage and hasPreviousPage booleans that control button state, optional startCursor / endCursor strings for your fetch callbacks, and an optional totalCount that, when provided, renders a "N records" label. The pageSize field controls the page-size selector's initial value.
isCursorPagination is active, the ?page and ?per_page URL parameters are not written because page numbers have no meaning in cursor-based pagination. Sorting URL sync still works normally.Row Selection
Row selection adds a checkbox column that lets users select individual rows or all rows on the current page. Selected rows can then be acted on via the floating bar, the toolbar delete button, or your own bulk-action logic.
Enable the feature with features.rowSelection: true in config. Row selection is defined in your column definitions, not inside TableCraft itself, which gives you full control over checkbox rendering, aria labels, and disabled states.
import { ColumnDef } from "@tanstack/react-table"
import { Checkbox } from "@/components/ui/checkbox"
const columns: ColumnDef<User>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected()}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
// ... remaining columns
]The reserved column id "select" is recognized by the column-visibility toggle and is excluded from the hide/show list automatically. When features.rowSelection is false, TanStack Table sets enableRowSelection: false on the instance — checkboxes may still render if you include the column definition, but toggling them produces no state changes.
Column Visibility
The column visibility toggle renders as a Columns dropdown button in the toolbar. It lists all data columns (those with an accessorFn or accessorKey where getCanHide() returns true) with checkboxes. Toggling a checkbox calls column.toggleVisibility(), which hides or shows that column instantly without a re-fetch.
Display columns with ids "select" and "actions" are excluded from the list. Set features.columnVisibility: false to hide the toggle button entirely and keep all columns permanently visible. Column visibility state is not currently synced to the URL.
CSV Export
The CSV export button downloads the currently selected rows as a .csv file. The button is disabled when no rows are selected, providing a clear affordance that selection is a prerequisite.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| isShowExportButtons.isShow | boolean | Yes | — | Show or hide the export button in the toolbar. |
| isShowExportButtons.ignoredCols | string[] | No | — | Column ids to exclude from the CSV output. The 'actions' and 'select' columns are always excluded regardless of this setting. |
| isShowExportButtons.fileName | string | No | table-export | Base filename for the downloaded CSV file (without extension). |
Behavior notes
- Uses the
export-to-csvlibrary for file generation and triggers a browser download with a synthetic anchor click. - Cell values are extracted from the visible cells of selected rows. Null, undefined, and empty string values are skipped.
- CSV column headers are derived from the column header text when available, falling back to the column's
id. - Enable the button by setting
features.csvExport: truein config (it is enabled by default) and passingisShowExportButtons.isShow: true.
View Toggle
The view toggle lets users switch between the default table layout and a responsive card grid layout. The toggle buttons appear in the top-left corner of the toolbar when features.viewToggle is true and the parent component supplies both viewMode and onViewModeChange props.
Card view details: cards are laid out in a responsive grid — 1 column on mobile, 2 columns at the sm breakpoint, and 3 columns at lg. Each card displays the row selection checkbox alongside the row number, followed by all visible data columns rendered as label: value pairs. When a card's row is selected, it receives a primary-color border and a subtle background tint to make the selection state obvious without relying on the checkbox alone.
Both views share the same TanStack Table instance and the same toolbar controls — sorting, filtering, pagination, and row selection work identically regardless of which view is active.
Floating Bar
The floating bar is a fixed-position action strip that slides in at the bottom of the viewport whenever at least one row is selected. It provides quick access to bulk actions without requiring the user to scroll back up to the toolbar.
Config flag: features.floatingBar — disabled by default. Enable it by setting this flag to true in provider or instance config, or passfloatingBar={true} directly to DataTable.
Visibility condition: The bar renders only when table.getFilteredSelectedRowModel().rows.length > 0. It disappears automatically when the selection is cleared.
Available actions:
- Clear selection — an X button that calls
table.resetRowSelection()and dismisses the bar. - Selection count — a read-only label showing how many rows are currently selected (e.g., 3 row(s) selected).
- Delete — rendered only when
deleteRowsActionis provided. Calls the suppliedMouseEventHandlerwith the click event so you can perform your own confirmation dialog and data mutation.
deleteRowsAction prop. The floating bar is the recommended pattern for bulk-delete UX because it remains visible regardless of scroll position and clearly communicates the number of affected rows.