Kombase
Components

Data Table

A powerful and flexible data table component for displaying, filtering, sorting, and paginating tabular data.

Reference the TanStack Table Column Definitions Guide for detailed column definition guide.

Features

  • Advanced Filtering - Multiple filter types (text, number, date, select, multi-select) with customizable operators
  • URL State Management - Sync table state with URL search params using nuqs
  • Sorting - Multi-column sorting with persistent state
  • Pagination - Server-side or client-side pagination with customizable page sizes
  • Column Visibility - Toggle column visibility with view options menu
  • Row Selection - Single or multi-row selection with action bar
  • Column Pinning - Pin columns to left or right for better UX
  • Keyboard Navigation - Full keyboard support with shortcuts for filter and sort menus
  • Responsive - Mobile-first design with overflow handling
  • Customizable - Flexible API for custom filters, columns, and actions

Layout

Import all components and combine them:

Walkthrough

Define columns with appropriate metadata:

Initialize the table state using the useDataTable hook:

Pass the table instance to the DataTable, and DataTableToolbar components:

Render an action bar on row selection:

useDataTable

A hook for initializing the data table with state management.

Overview

The useDataTable hook acts as an intelligent wrapper around useReactTable from @tanstack/react-table. It seamlessly handles:

  • Server-side pagination, sorting, and filtering
  • Controlled or uncontrolled state for page and page size
  • Debounced column filter updates (defaults to 300ms) to prevent excessive API calls
  • Row selection and column visibility management

Import

Usage

Basic Setup

First, define your columns. Use the meta property to configure filter behaviors.

When fetching data from an external API, it's highly recommended to control the pagination state externally.

Uncontrolled Pagination

If you don't provide page, perPage, or onPageChange, the hook will manage the state internally.

Column Filtering

Column filtering is enabled by setting enableColumnFilter: true in your column definitions. You can configure column filtering in three different modes:

1. Controlled Column Filtering (Standard SSR / Server-Side Filtering)

In standard server-side filtering (SSR), each column filter maps to its own direct URL query parameter (e.g. ?search=xyz&status=paid,pending).

By passing filterValues and onFilterValuesChange to useDataTable, the table becomes a fully controlled component synced with your URL parameters. When a user types in a text filter or checks a faceted filter:

  1. The table automatically triggers onColumnFiltersChange (with built-in debounce for text inputs).
  2. It calls onFilterValuesChange with the updated keys and values.
  3. You write the updates back to the URL search params, which triggers a reload/fetch.

Here is a complete step-by-step walkthrough for React Router v7 / Remix:

Step-by-Step Implementation
  1. Read URL Params: Read the active parameters using useSearchParams.
  2. Configure useDataTable: Pass the parsed values to filterValues and handle state updates in onFilterValuesChange.
  3. Reset Page on Filter Change: Always reset the page parameter to 1 when filters are modified to prevent displaying empty pages.
  4. Fetch Data: Pass the active URL parameters directly to your backend API fetching hook.
Code Example

2. Client-Side Column Filtering (Without URL parameters)

If you just want local in-memory filtering in the browser without query parameters:

  1. Fetch all data from the database at once.
  2. Set manualFiltering: false.
  3. Do not pass filterValues or onFilterValuesChange.

3. API Parameter Helper: resolveFiltersToFlatParams

When integrating with server-side processing, frontend filter states (like TanStack Column Filters) need to be serialized into query parameters matching your backend API specification.

Instead of writing manual mapper functions for every column, the built-in resolveFiltersToFlatParams utility automatically converts an array of ExtendedColumnFilter objects into a flat key-value query object. It supports various backend filter styles (Nested, Django, PostgREST, MongoDB, etc.), range decomposition, and customizable array formatting out of the box.

Import
Configuration Options

The helper accepts a second argument config of type FilterResolutionConfig:

PropertyTypeDescription
style'flat' | 'suffix' | 'django' | 'nested' | 'prefix' | 'postgrest' | 'custom'Naming/structure convention for query keys. Default is 'flat'.
operatorMapPartial<Record<FilterOperator, string>>Custom mapping of operators to backend representation (e.g., { inArray: 'in' }).
arrayFormat'comma' | 'repeat' | 'brackets'Formats multi-select array values (e.g., comma -> a,b, repeat -> [a, b], brackets -> status[]=[a,b]). Default is 'comma'.
dateFormatstringCustom date formatting string (dayjs). Default is 'YYYY-MM-DD'.
flatRangeSuffix[string, string]Suffixes for start/end in range queries when style is flat. Default is ['_min', '_max'] for numbers and ['_start', '_end'] for dates.
formatFilterFunctionCustom hook to manually format any parameter key-value pair.
joinParamNamestringParameter key to represent the logical join operator (e.g. _join -> ?_join=or).
joinOperator'and' | 'or'Active logical join operator.
columnOptionsMapRecord<string, string[]>Valid options for computing complement arrays (for ne / notInArray) on flat APIs.
paramNameMapRecord<string, string>Rename parameter keys (e.g. { ticket_status: "status" }).
customMappersPartial<Record<keyof TData, (value: any, operator: FilterOperator) => Partial<TParams>>>Per-column custom serializer overrides.

Guide: Backend Query Styles

Let's assume we have a filter on the column age with a value of 18 and the operator is gte (Greater than or equal). Here is how each style configuration formats the query parameter:

Style NameParameter StructureOutput ExampleTypical Backend / Target Framework
flatkey=value?age=18Express, Laravel (Simple query mapping)
suffixkey_operator=value?age_gte=18Custom REST APIs, Django (legacy / flat)
djangokey__operator=value?age__gte=18Django ORM / Django Filter
nestedkey[operator]=value?age[gte]=18Ruby on Rails, NestJS (TypeORM), Strapi, Koa
prefixkey[$operator]=value?age[$gte]=18FeathersJS, MongoDB Query Syntax
postgrestkey=operator.value?age=gte.18PostgREST, Supabase REST API

[!TIP] If style is 'flat' (default), operators are omitted in the keys (except for isEmpty/isNotEmpty which generates key_is_empty=true/false). For other styles, operators are automatically mapped and embedded.


Guide: Range Filters Decomposition (isBetween)

When a developer uses a numeric range (like a slider) or a date range picker, the table filter outputs an operator isBetween with an array value: [minValue, maxValue].

resolveFiltersToFlatParams handles this intelligently based on the chosen style:

  1. For Flat Style (style: 'flat'):
    • Numeric Range: Decomposes into _min and _max keys. { price: [10, 50] } $\rightarrow$ ?price_min=10&price_max=50
    • Date Range: Decomposes into _start and _end keys. { createdAt: ['2023-01-01', '2023-01-31'] } $\rightarrow$ ?createdAt_start=2023-01-01&createdAt_end=2023-01-31
    • You can customize these suffixes globally using flatRangeSuffix: ['_min_suffix', '_max_suffix'].
  2. For Non-Flat Styles (e.g. nested, suffix, postgrest):
    • The helper automatically decomposes isBetween into two separate operations: gte (start) and lte (end), then formats them according to the style rules:
      • style: 'nested' $\rightarrow$ ?price[gte]=10&price[lte]=50
      • style: 'suffix' $\rightarrow$ ?price_gte=10&price_lte=50
      • style: 'postgrest' $\rightarrow$ { price: ['gte.10', 'lte.50'] } (which gets parsed as ?price=gte.10&price=lte.50)

Guide: Array Formatting for Multi-Select (arrayFormat)

For columns that support selecting multiple values (e.g., status: ['active', 'inactive']), you can configure how the array values are sent to your API using arrayFormat:

  • comma (Default): Combines elements into a single comma-separated string.
    • Output: ?status=active,inactive
  • repeat: Generates a standard JavaScript array value, which standard query-string serializers repeat.
    • Output: ?status=active&status=inactive
  • brackets: Appends [] to the key name.
    • Output: ?status[]=active&status[]=inactive

Guide: Negation Filtering & columnOptionsMap

When a developer uses a negation filter (such as "Is not" or "Has none of"), the helper resolves it differently depending on your API structure:

  1. For Flat Style (style: 'flat'):
    • Flat APIs typically do not understand operators like ne or notIn.
    • To support these, you must provide the list of all available options for that column via columnOptionsMap.
    • The helper will compute the options complement at frontend-level (e.g. if status options are ['A', 'B', 'C'] and you exclude 'A', the helper sends ?status=B,C).
  2. For Non-Flat Styles (e.g. nested, suffix, postgrest):
    • Standard backends support negation natively (e.g., status[ne]=pending or status=neq.pending).
    • Therefore, you do not need columnOptionsMap for these styles. You can safely omit it, and the helper will map the negative operator directly into the query key/value.

Usage Examples
1. Rails / NestJS (Nested Brackets style)
2. Django style API
3. PostgREST / Supabase API
4. Fully Custom Hook Override

If your backend requires a very unique format, you can intercept the format logic:

Props

The useDataTable hook accepts the following properties.

Prop

Type

Returns

The useDataTable hook returns an object with the following properties.

Prop

Type

Column Meta for Filter Variant

Utilize the meta property in your column definitions to configure the UI representation of the filter:

Loading State (DataTableSkeleton)

DataTableSkeleton is used to display a loading skeleton that strictly matches the actual layout of the data table. This is extremely useful when combined with React.Suspense or when awaiting data from an API.

Skeleton Usage

Suspense Integration

The recommended pattern when using frameworks like Next.js or React Router v7 with asynchronous data fetching:

Skeleton Props

PropTypeDefaultDescription
columnCountnumberRequired. The number of columns the skeleton should render.
rowCountnumber10The number of row skeletons to display.
filterCountnumber0The number of filter skeleton boxes to display in the top toolbar.
cellWidthsstring[]['auto']An array of column widths (e.g., ['100px', '200px']).
withViewOptionsbooleantrueWhether to render the skeleton for the column visibility toggle button.
withPaginationbooleantrueWhether to render the skeleton for the bottom pagination controls.
shrinkZerobooleanfalseIf true, columns will possess a min-width matching their respective cellWidths.
classNamestringCustom CSS class for the main container.

API Reference

DataTable Props

Props for the main <DataTable /> component:

PropTypeDefaultDescription
tableTable<TData>Required. The table instance returned by useDataTable.
actionBarReact.ReactNodeOptional action bar component to display when rows are selected.
wrapperClassnamestringCustom className for the table's scrollable wrapper container.
emptyStateReact.ReactNode'No results.'Custom content (string or ReactNode) to show when the table has no records.
stickyHeaderbooleanfalseIf true, the table header will stick to the top of the container during vertical scroll.
onEndReached() => voidCallback function triggered when scrolling near the bottom of the table wrapper (infinite scroll).
isLoadingMorebooleanfalseUsed with onEndReached to prevent duplicate load requests when fetching more data.
loadingboolean | React.ReactNodefalseDisplay a blur overlay and spinner in the center of the table (initial load).
isLoadingbooleanfalseDeprecated. Use isLoadingMore instead.
endReachedThresholdnumber100The distance (in pixels) from the bottom of the container to trigger the onEndReached callback.

DataTablePagination Props

Props for the <DataTablePagination /> component:

PropTypeDefaultDescription
tableTable<TData>Required. The table instance returned by useDataTable.
pageSizeOptionsnumber[][10, 20, 30, 40, 50]Options for the page size select dropdown.
showPageNumbersbooleantrueWhether to show the "Rows per page" text and the current page info.
rowsPerPageTextReact.ReactNode'Rows per page'Custom text or ReactNode to display next to the page size select dropdown.
pageText(page: number, total: number) => React.ReactNodeCustom formatting function to render the current page status text (e.g. "Page X of Y").

Column Definitions

The column definitions are used to define the columns of the data table.

Properties

Core configuration options for defining columns.

PropDescription
idRequired: Unique identifier for the column
accessorKeyRequired: Key to access the data from the row
accessorFnOptional: Custom accessor function to access data
headerOptional: Custom header component with column props
cellOptional: Custom cell component with row props
metaOptional: Meta options for accessing column metadata
enableColumnFilterBy default, the column will not be filtered. Set to `true` to enable filtering
enableSortingEnable sorting for this column
enableHidingEnable column visibility toggle

Column Meta

Column meta options for filtering, sorting, and view options.

PropDescription
labelThe display name for the column
placeholderThe placeholder text for filter inputs
variantThe type of filter to use (`text`, `number`, `select`, etc.)
optionsFor select/multi-select filters, an array of options with `label`, `value`, and optional `count` and `icon`
loadingFor select/multi-select filters, whether the options are currently loading from an API (displays a spinner loader)
rangeFor range filters, a tuple of `[min, max]` values
unitFor numeric filters, the unit to display (e.g., 'hr', '$')
iconThe react component to use as an icon for the column
presetsFor `dateRange` filters, a boolean to enable the default quick selection presets panel or an array of custom `DatePreset` objects
alignThe alignment of the column content and header (`left`, `center`, `right`)
thClassAdditional CSS class names to apply to the table header cell (`<TableHead>`)
cellClassAdditional CSS class names to apply to the table body cell (`<TableCell>`)

Filter Variants

Available filter variants for column meta.

TitleDescription
textText search with contains, equals, etc.
numberNumeric filters with equals, greater than, less than, etc.
rangeRange filters with minimum and maximum values
dateDate filters with equals, before, after, etc.
dateRangeDate range filters with start and end dates
booleanBoolean filters with true/false values
selectSingle-select filters with predefined options
multiSelectMulti-select filters with predefined options

How is this guide?

On this page