API Reference
DataTableProps<T>
Required props:
rows: T[]columns: DataTableColumn<T>[]getRowId: (row: T) => string
The base row model, sorting pipeline, and row-selection state are powered by TanStack Table. The public API remains package-specific so consumers do not need to wire TanStack directly for standard table screens.
Core integration props:
groups?: DataTableGroup<T>[]selectedIds?: string[]onSelectedIdsChange?: (selectedIds: string[]) => voidsort?: { columnId: string; direction: "ascending" | "descending" }defaultSort?: { columnId: string; direction: "ascending" | "descending" }onSortChange?: (sort) => voidmanualSorting?: booleanfilters?: Record<string, unknown>defaultFilters?: Record<string, unknown>onFiltersChange?: (filters) => voidmanualFiltering?: booleanquickSearch?: stringdefaultQuickSearch?: stringonQuickSearchChange?: (quickSearch) => voidcolumnVisibility?: Record<string, boolean>defaultColumnVisibility?: Record<string, boolean>onColumnVisibilityChange?: (visibility) => voidcolumnOrder?: string[]defaultColumnOrder?: string[]onColumnOrderChange?: (order) => voidenableColumnReordering?: booleancolumnSizing?: Record<string, number>defaultColumnSizing?: Record<string, number>onColumnSizingChange?: (sizing) => voidcolumnPinning?: { left?: string[]; right?: string[] }defaultColumnPinning?: { left?: string[]; right?: string[] }onColumnPinningChange?: (pinning) => voideditingCell?: { rowId: string; columnId: string }defaultEditingCell?: { rowId: string; columnId: string }onEditingCellChange?: (cell) => voidonCellEditCommit?: ({ row, rowId, column, value }) => void | Promise<void> | { close?: boolean; error?: ReactNode }loading?: booleanerror?: boolean | Errorstale?: booleanserverVirtualization?: DataTableServerVirtualization<T>totalRowCount?: numberrowIndexOffset?: numberloadingLabel?: stringemptyLabel?: stringerrorLabel?: stringloadingState?: DataTableStateLabelemptyState?: DataTableStateLabelerrorState?: DataTableStateLabelrenderRowActions?: (row: T) => ReactNoderenderCard?: (row: T) => ReactNodetoolbar?: boolean | DataTableToolbarConfig<T>renderToolbar?: (context) => ReactNoderenderFooter?: (context) => ReactNoderenderGroupHeader?: (group, summary, context) => ReactNoderenderMobileGroupHeader?: (group, summary, context) => ReactNodecollapsedGroupIds?: string[]defaultCollapsedGroupIds?: string[]onCollapsedGroupIdsChange?: (groupIds) => void
Layout and behavior:
height?: numbermobileHeight?: numberrowHeight?: numbergroupHeight?: numberminWidth?: stringdensity?: "compact" | "comfortable"motion?: "system" | "always" | "reduced"isRowSelectable?: (row: T) => booleanrowAriaLabel?: (row: T) => stringariaLabel?: stringariaLabelledBy?: stringonRowClick?: (row: T) => voidonRowContextMenu?: (row: T, event: MouseEvent) => voidicons?: Partial<DataTableIcons>className?: stringtableClassName?: string
Selection behavior:
- Omit both
selectedIdsandonSelectedIdsChangefor a non-selectable table. - Pass
onSelectedIdsChangefor mutable selection.selectedIdsmay be controlled or omitted for internal selection state. - Pass
selectedIdswithoutonSelectedIdsChangeonly when selection is read-only; checkboxes render disabled so the current selection can be inspected but not changed. - Use
isRowSelectablefor locked, permission-restricted, archived, or failed rows. Disabled rows remain visible but their checkboxes cannot be changed, and select-all skips them.
Row activation behavior:
onRowClickmakes desktop rows and mobile cards keyboard reachable with Enter and Space activation.- Native interactive descendants such as buttons, links, inputs, selects, and textareas do not bubble into row activation.
- Common ARIA interactive roles and focusable custom controls are also treated as row-activation boundaries.
- Add
data-rdtg-stop-row-clickto custom composite widgets inside cells or cards when the widget should fully own pointer and keyboard events.
Accessibility metadata:
- Provide
ariaLabelorariaLabelledByso the desktop grid and mobile list have a stable accessible name. - The desktop grid exposes
aria-rowcount,aria-colcount, headeraria-colindex, rowaria-rowindex, and cellaria-colindexmetadata. These indexes account for selection and action columns and remain meaningful when the desktop body is virtualized. - For ungrouped server-paged data, pass
totalRowCountand zero-basedrowIndexOffsetsoaria-rowcount,aria-rowindex, and slot summaries describe the full result set instead of only the loaded page. - Desktop and mobile bodies virtualize rendered items. Server virtualization callbacks report the active surface only, so hidden responsive renderers do not duplicate load requests.
- Desktop keyboard navigation supports Arrow keys, Home/End, Ctrl/Cmd+Home, Ctrl/Cmd+End, and PageUp/PageDown. Page movement uses the configured row height and table height to move by a visible viewport-sized row step.
- Use
rowAriaLabelwhen the visible cells do not clearly identify a row for row-level activation.
Integration slots:
- Use
toolbarwhen a screen should get package-owned standard controls such as quick search and column visibility controls. Passtoolbar={true}for the default quick-search plus column-controls toolbar, or configure{ quickSearch, columnVisibility, renderActions, renderSummary }. - Use
renderToolbarandrenderFooterfor fully app-owned controls, saved-view controls, summaries, pagination controls, or app-specific actions. - Slots receive
{ rows, columns, visibleColumns, quickSearch, setQuickSearch, columnVisibility, setColumnVisibility, columnOrder, setColumnOrder, columnSizing, setColumnSizing, columnPinning, setColumnPinning, visibleRows, visibleItems, visibleRowCount, totalRowCount, rowIndexOffset, selectedIds, selectedCount, allVisibleSelected, someVisibleSelected, sort, filters, loading, error, stale }. - The table does not own pagination, navigation, exporting, or mutations inside these slots. Consumers render those controls and wire them to their app state.
totalRowCountdefaults to the loaded visible row count. When a server result set is larger than the suppliedrows, pass the known total count and the zero-based offset of the first loaded row so pagination footers can render ranges such as251-300 of 842.
Server virtualization:
serverVirtualization.overscan?: numbercontrols virtual overscan and defaults to8.serverVirtualization.loadThreshold?: numbercontrols when end-reached callbacks fire and defaults to8.hasMoreRows,loadingMore, andloadMoreErrordescribe ungrouped append state. IfhasMoreRowsis omitted with a knowntotalRowCount, it defaults tototalRowCount > rowIndexOffset + visibleRowCount. If the total is unknown andonRowsEndReachedis supplied, the table treats the stream as open-ended.retryKeylets the host intentionally re-arm an end-reached request for the same loaded window after retry state changes.showEndSentinelcontrols whether a known completed stream renders the built-in end sentinel. It defaults totruewhenhasMoreRowsortotalRowCountmakes the end knowable.renderLoadMore(context)customizes loading, error, and end sentinels.context.defaultContentcontains the package default, andcontext.statusis"loading","error", or"end".onRowsRangeChange(range)receives the active surface, absolute[startIndex, endIndex)range, loaded rows,loadedCount,totalRowCount, androwIndexOffset.onRowsEndReached(request)fires once per loaded window when the active range reaches the load threshold.requestedStartIndexis the next absolute server row index.onGroupRangeChange(range)andonGroupEndReached(request)use group-local server indexes. If the group rows start after index0, setgroup.rowIndexOffset;requestedStartIndexisrowIndexOffset + loadedCount.- The table renders inline loading, error, and end sentinels for opted-in server virtualization state. These sentinels are not selectable, editable, or keyboard cell targets.
First-class toolbar:
toolbar={true}renders quick search, column controls, and no host actions. Configuretoolbar.quickSearchortoolbar.columnVisibilityasfalseto remove either standard control.toolbar.quickSearchaccepts{ label, placeholder, clearLabel }. The value is backed byquickSearch,defaultQuickSearch, andonQuickSearchChange, and is also exposed to custom slots asquickSearchandsetQuickSearch.- Local quick search is TanStack global filtering. Columns can provide
quickSearchTextfor custom cell templates, rely onsortAccessor, or fall back to simple text rendered byrenderCell. SetquickSearchable: falseto exclude a column. - With
manualFiltering, quick search remains visible state but does not filter the suppliedrows; useonQuickSearchChangeto update server query state and feed the next page of rows back into the table. toolbar.columnVisibilityaccepts{ label, resetLabel, emptyLabel, columnIds, allowHideAll }. The menu updates the same TanStack-backed column visibility state asdefaultColumnVisibility,columnVisibility, andsetColumnVisibility.- Set
hideable: falseon columns that should never appear in the built-in column-controls menu.
DataTableToolbarConfig<T>:
ariaLabel?: stringquickSearch?: boolean | DataTableToolbarQuickSearchConfigcolumnVisibility?: boolean | DataTableToolbarColumnVisibilityConfigrenderActions?: (context: DataTableRenderContext<T>) => ReactNoderenderSummary?: (context: DataTableRenderContext<T>) => ReactNode
DataTableToolbarQuickSearchConfig:
label?: stringplaceholder?: stringclearLabel?: string
DataTableToolbarColumnVisibilityConfig:
label?: stringresetLabel?: stringemptyLabel?: stringcolumnIds?: string[]allowHideAll?: boolean
DataTableRenderContext<T>:
rows,columns,visibleColumnsquickSearch,setQuickSearchcolumnVisibility,setColumnVisibilitycolumnOrder,setColumnOrdercolumnSizing,setColumnSizingcolumnPinning,setColumnPinningvisibleRows,visibleItems,visibleRowCount,totalRowCount,rowIndexOffsetselectedIds,selectedCount,allVisibleSelected,someVisibleSelectedsort,filters,loading,error,stale
Related context and updater exports:
DataTableQuickSearchUpdateris the accepted value forsetQuickSearch.DataTableColumnVisibilityUpdater,DataTableColumnOrderUpdater,DataTableColumnSizingUpdater, andDataTableColumnPinningUpdaterare the accepted values for the corresponding setter functions inDataTableRenderContext.DataTableVisibleItem<T>describes entries invisibleItems; each item is a group item, a row item, or an opted-in server virtualization sentinel with{ kind: "loadMore", scope, status }.DataTableColumnContext<T>is passed torenderCell,quickSearchText,editable, andgetEditValue.DataTableFilterContext<T>is passed to customfilterControlrender functions.DataTableFilterActiveContext<T>is passed tofilterActivefunctions.DataTableColumnFilterContext<T>is passed tofilterFn.DataTableEditCellContext<T>is passed torenderEditCell.DataTableCellEditCommit<T>is passed toonCellEditCommit.DataTableCellEditCommitResultis the optional result returned byonCellEditCommitto keep the editor open or show validation feedback.DataTableGroupSummary<T>andDataTableGroupHeaderContext<T>are passed to group summary/header render functions.
Headless entrypoint:
- Import
@flownamix/react-data-grid-kit/headlesswhen app-owned saved-view stores, server adapters, or tests need the package model helpers without importing the React component or stylesheet. - The headless entrypoint exports grouping, filtering, row-selection, and sorting helpers such as
groupRows,filtersToColumnFilters,rowMatchesQuickSearch,applyRowSelectionUpdate, andnextSort. - It also exports the public integration types, including
DataTableColumnVisibilityState,DataTableColumnOrderState,DataTableColumnSizingState,DataTableColumnPinningState,DataTableRenderContext, andDataTableRowId, so shared utilities can stay type-aligned with the component API.
Column visibility:
- Column visibility is backed by TanStack Table state. Use
defaultColumnVisibilitywhen the table can own the current view, orcolumnVisibilitywithonColumnVisibilityChangewhen the URL, saved view, user preference store, or parent app owns it. - Visibility values are keyed by column id.
falsehides a column; missing keys andtruevalues are visible. - Hidden columns are removed from desktop headers, desktop cells, built-in mobile fields, keyboard navigation, and
aria-colcount/aria-colindexcalculations. Selection and action columns keep their own indexes. - The built-in
toolbarcolumn-controls menu reads and updates this same state. Sethideable: falseon columns that should stay locked in a saved view. renderToolbarreceives the fullcolumnslist, the currentvisibleColumns, the currentcolumnVisibility, andsetColumnVisibilityso a host can render a column chooser without duplicating package state.- Custom
renderCardmobile content remains app-owned. If a card should honor column visibility, use controlled state in the host or render the built-in mobile fields instead.
Column ordering:
- Column ordering is backed by TanStack Table state and expressed as an array of column ids. The array describes the preferred data-column order; missing known columns keep their original
columnsprop order after the ordered ids. - Use
defaultColumnOrderwhen the table can own saved-view defaults, orcolumnOrderwithonColumnOrderChangewhen URL state, user preferences, or a parent grid shell owns the order. - Set
enableColumnReorderingto render desktop header drag handles that update the same column-order state. Setreorderable: falseon a column that should stay out of header drag/drop. - Ordering affects desktop headers, desktop cells, built-in mobile fields, keyboard navigation, and
visibleColumnsin integration slots. CustomrenderCardmobile content remains app-owned. - Column visibility and pinning are applied through TanStack's visible leaf column model. Hidden columns are omitted, and pinned columns are rendered in left, center, and right regions while preserving the relevant saved-view order.
renderToolbarreceivescolumnOrderandsetColumnOrderso a host can render order presets, reset actions, persistence controls, or a drag-and-drop column manager without duplicating package state.
Column sizing:
- Column sizing is backed by TanStack Table state and expressed as pixel widths keyed by column id.
- Use
defaultColumnSizingwhen the table can own the current widths, orcolumnSizingwithonColumnSizingChangewhen saved views, URL state, user preferences, or a parent grid shell owns them. widthremains the initial CSS grid track for columns without sizing state. Once a column has a sizing value, desktop headers and rows use a pixel track such as240px.- Set
resizable: trueon a column to render a resize separator in the desktop header. UseminWidthandmaxWidthto bound pointer and keyboard resizing. - Resize separators are labelled vertical separators with
aria-valuemin,aria-valuemax, andaria-valuenow. ArrowLeft/ArrowRight resize in small steps, Shift+ArrowLeft/Shift+ArrowRight resize in larger steps, and Home/End jump to the configured minimum or maximum. renderToolbarreceivescolumnSizingandsetColumnSizingso a host can render saved-view width presets, reset actions, or persistence controls without duplicating package state.
Column pinning:
- Column pinning is backed by TanStack Table state and expressed as
{ left?: string[]; right?: string[] }, keyed by column id. - Use
defaultColumnPinningwhen the table can own saved-view defaults, orcolumnPinningwithonColumnPinningChangewhen URL state, user preferences, or a parent grid shell owns the pinned layout. - Pinned desktop data columns are reordered into left, center, and right regions using TanStack's visible leaf column model. Built-in mobile fields receive the same visible column order; custom
renderCardcontent remains app-owned. - Desktop pinning uses native sticky positioning inside the table's scroll frame, so body cells stay locked without scroll-linked transform updates.
- When left data columns are pinned, the selection column becomes sticky so pinned cells do not cover row checkboxes. When right data columns are pinned, the actions column becomes sticky for the same reason.
- Sticky offsets are calculated from column sizing state and pixel
widthvalues. For non-pixel flexible tracks, the table falls back to the package's default column width, so persisted pinned layouts should pair pinning with pixel sizing for the pinned columns. renderToolbarreceivescolumnPinningandsetColumnPinningso a host can render saved-view pin presets, clear actions, or persistence controls without duplicating package state.
State panels:
loading,error, and the derived empty state choose which state panel is shown when there are no visible rows or the table cannot render row content.loadingLabel,emptyLabel, anderrorLabelare simple string fallbacks.loadingState,emptyState, anderrorStateaccept{ title, description, action }for richer panels.stalekeeps the current rows visible and marks the table as refreshing instead of replacing the table with a loading panel.
Sorting behavior:
- Local sorting requires
sortable: trueandsortAccessoron the column. sort/defaultSortcontrol the visible sort state. They do not make sorting server-owned by themselves.- Header activation cycles the active column through ascending, descending, and no sort. In uncontrolled local mode, clearing sort restores the supplied row order.
- Passing
sort={undefined}explicitly keeps sort controlled in an empty state. Header clicks callonSortChange, but the table does not update sort UI or row order until the host supplies the nextsortvalue. - Controlled and manual integrations receive
onSortChange(undefined)when the active sort is cleared. - Set
manualSortingwhen the server, URL state, saved view, or parent application owns row order. In manual mode, clicking a sortable header updates sort state throughonSortChange, but the table preserves the suppliedrowsorder. - Accessorless sortable columns are disabled in local mode and enabled in manual mode, so server-backed tables can expose sort fields that do not map cleanly to a local primitive accessor.
DataTableColumn<T>
id: stringheader: ReactNodewidth?: stringminWidth?: numbermaxWidth?: numberhideable?: booleanreorderable?: booleanresizable?: booleansortable?: booleansortAccessor?: (row: T) => string | number | boolean | Date | null | undefinedquickSearchable?: booleanquickSearchText?: (row: T, context) => string | number | boolean | Date | null | undefinedfilterControl?: ReactNode | ((context) => ReactNode)filterActive?: boolean | ((context) => boolean)filterFn?: (row, value, context) => booleanfilterLabel?: stringeditable?: boolean | ((row, context) => boolean)getEditValue?: (row, context) => unknownrenderEditCell?: (context) => ReactNoderenderCell: (row: T, context) => ReactNodeclassName?: string | ((row: T) => string | undefined)align?: "start" | "center" | "end"hideOnMobile?: boolean
Column notes:
widthis a CSS grid track used before a column has pixel sizing state. Use strings such as160px,minmax(220px, 2fr), or1fr.minWidthandmaxWidthbound resizable columns and keyboard resizing.alignaffects both header and cell alignment.classNameis applied to desktop data cells for that column. Use it for narrow semantic styling hooks, not domain layout.hideOnMobileremoves the column from the built-in mobile field layout. It does not affect customrenderCardcontent.
Filter render functions receive { value, filters, setFilter, clearFilter, close }. Values can store date ranges, server facet objects, saved-view IDs, or multi-field query models instead of being limited to predefined option lists.
Filter trigger buttons expose aria-expanded, aria-controls, and active state. Open filter content is rendered as a labelled dialog, focuses the first reachable custom control, closes on Escape or outside interaction, and returns focus to the trigger.
When a column provides filterFn, the package adapts filters into TanStack column filters and applies client-side filtering. Omit filterFn for server-owned filters, or set manualFiltering to keep filter state visible while leaving the supplied rows untouched.
Inline edit render functions receive { row, rowId, column, value, setValue, commit, cancel, pending, error, errorId }.
- Set
editableandrenderEditCellon columns that can be edited. - Render whatever control matches the data shape: text inputs, selects, segmented controls, date pickers, or compact icon actions. The table provides the lifecycle and focus contract; the editor markup stays app-owned.
- Use
getEditValueto derive the initial draft value from the row. This value is also used when a host app opens an editor witheditingCellordefaultEditingCell. - Use
defaultEditingCellfor uncontrolled initial edit state, oreditingCellwithonEditingCellChangewhen the host app controls which cell is being edited. - Passing
editingCell={undefined}explicitly keeps editing controlled in a closed state. Edit triggers callonEditingCellChange, but the editor does not open until the host supplies the requested cell. - When a controlled host switches
editingCellto another cell, the table resets the previous draft value, validation message, and pending state. Any stale async commit from the previous cell is ignored so it cannot close or dirty the newly active editor. - While a controlled editor is open, the displayed edit value follows refreshed
rowsuntil the user changes the draft. After the draft is dirty, later server refreshes do not overwrite the user's in-progress edit. - When the edited row or column leaves the visible model because of column visibility, filtering, grouping collapse, or editability changes, the table resets the draft, pending state, and validation message. Controlled hosts receive
onEditingCellChange(undefined), and stale async commits from the removed editor are ignored. - Editors focus their first focusable control when opened, Escape cancels editing, and Ctrl+Enter or Cmd+Enter commits the current value. Custom editors may still provide explicit save/cancel controls or field-specific shortcuts.
commit()commits the current edit value.commit(value)commits the provided value, including explicitundefined.onCellEditCommitreceives the committed value. The table does not mutate rows; the host app updates its data source after validation or persistence.- Commit handlers may return a promise. While it is pending, the editor remains open,
pendingistrue, duplicate commits are ignored, and Escape cancel is blocked so an in-flight save is not hidden accidentally. - Successful commits close by default. Return
{ close: false }to keep editing open after a successful save, or{ close: false, error: "Message" }to keep it open with an inlinerole="alert"validation message. Thrown or rejected errors also keep the editor open and render the error message. - Use
errorIdwitharia-describedbyandaria-invalidinside custom controls whenerroris present. The table assigns the same id to the package-rendered alert so field-level validation is announced from the focused editor control. - The built-in mobile field layout honors editable columns with the same editing state and keyboard contract. When
renderCardis supplied, mobile card markup is fully app-owned and should provide its own edit controls where needed.
DataTableGroup<T>
id: stringlabel: ReactNoderows?: T[]rowIds?: string[]totalCount?: numberloadedCount?: numberrowIndexOffset?: numberhasMoreRows?: booleanloadingMore?: booleanloadMoreError?: ReactNodecountLabel?: ReactNodestate?: "loaded" | "loading" | "error" | "empty" | "partial"summary?: ReactNode | ((summary) => ReactNode)progressLabel?: ReactNodeprogressValue?: numbercollapsible?: booleandefaultCollapsed?: booleandepth?: numberactions?: ReactNode
Grouped tables use the same visible item model on desktop and mobile. Mobile cards keep group headers, collapse state, group metadata, and row selection instead of flattening grouped data.
When collapsible is false, the group cannot be collapsed by defaultCollapsed, controlled collapsedGroupIds, built-in toggles, or a custom group header calling context.toggle.
Group notes:
rowIdsreferences rows from the top-levelrowsprop. Use it when the server returns groups and a shared result row array.rowsembeds group-specific rows. Use it when the group payload already owns its row array.totalCountdescribes the full server-side group size.loadedCountdescribes rows available to the table. If omitted,loadedCountfalls back to visible loaded rows.rowIndexOffsetis the zero-based group-local server index for the first loaded row in that group.hasMoreRowsoverrides the group append default. If omitted, a group has more rows whentotalCount > loadedCountorstateis"partial".loadingMoreandloadMoreErrorrender inline group sentinels whenserverVirtualizationis enabled.countLabeloverrides the built-in count text.summaryrenders additional group metadata. If it is a function, it receives{ group, visibleRows, totalCount, loadedCount }.progressValueis a percentage from0to100;progressLabellabels the progress.statechanges visual treatment for loading, error, empty, partial, and loaded groups.depthindents nested or hierarchical group rows.actionsrenders package-owned group action content in the built-in group header.
DataTableGroupState is the exported union for group state values: "loaded", "loading", "error", "empty", and "partial".
DataTableGroupSummary<T> includes group, visibleRows, totalCount, and loadedCount. It is used by group summaries, renderGroupHeader, and renderMobileGroupHeader.
DataTableGroupHeaderContext<T> includes group, summary, collapsed, collapsible, icons, and toggle. Use it when custom group headers need to preserve the built-in collapse behavior.
DataTableStateLabel
title?: ReactNodedescription?: ReactNodeaction?: ReactNode
Use state labels for loading, empty, and error panels when a screen needs more than the simple label props.
DataTableIcons
SortFilterExpandEditLoadingMore
Pass icons?: Partial<DataTableIcons> to override only the icons needed by an application. Icon components receive className, size, optional title, and icon-specific state props such as sort direction, filter active state, or expanded state.
DataTableIconProps is the shared prop contract for icon components. The package also exports defaultIcons and the default icon components SortIcon, FilterIcon, ExpandIcon, EditIcon, CheckIcon, CloseIcon, LoadingIcon, and MoreIcon.
Scalar Type Aliases
DataTableRowIdis the string row identifier returned bygetRowId.DataTableSortDirectionis"ascending"or"descending".DataTableDensityis"compact"or"comfortable".DataTableMotionPreferenceis"system","always", or"reduced".