Skip to content

Basics

Loading example…

Each filterable column gets a control, and the control comes from the column: a number column filters by range, a date column by date, a column with filterOptions by selection. You can override it with filterVariant.

The Filters button in the toolbar carries a badge with the number of active filters and toggles the panel. Active filters also appear as removable chips under the toolbar when showActiveFilterChips is on, which is what makes "why is this table empty?" answerable at a glance.

Where the controls live

Loading example…

filterDisplayMode decides the surface, not the behaviour:

ModeWhat the reader sees
'panel'A collapsible grid of controls under the toolbar. The default.
'popover'The same grid, anchored to the Filters button.
'row'A second header row with one input per column.
'none'No built-in UI; drive state.columnFilters yourself.

Loading example…

Independently of the mode, enableColumnActions puts a Filter… entry in each header menu that opens just that column's filter.

Applying instantly or on demand

filterApplyMode: 'manual' adds an Apply button, so a reader can set three filters before the table moves. Combined with filterDebounceMs on text inputs, it is how a server-mode table avoids a request per keystroke.

Choosing the comparison

filterFn picks the comparison and filterOperators decides which ones the reader may choose from the operator dropdown. The built-ins cover text (includesString, startsWith, endsWith, equalsString, fuzzy), numbers (equals, inNumberRange), dates (dateEquals, before, after, inDateRange), arrays (arrIncludes, arrIncludesAll, arrIncludesSome) and emptiness (empty, notEmpty).

col.accessor('city', { filterVariant: 'multiSelect', filterFn: 'arrIncludesSome' });
col.accessor('price', { type: 'number', filterVariant: 'rangeSlider' });

Filtering on the server

Filters serialize into the filters array of the TableQuery, using filterServerKey when your API names a field differently. Option lists come from dataSource.fetchFacets instead of the loaded rows; see Faceting.

Customization

renderFilter replaces one column's control while the table keeps owning the value:

col.accessor('assignee', {
  filterVariant: 'custom',
  renderFilter: ({ value, setValue }) => <UserPicker value={value} onChange={setValue} />,
});

The panel, the chips row and the Filters button are all slots, and <DataTable.FilterPanel /> places the panel anywhere on the page.

Limitations

  • A column has one active filter at a time. Two conditions on the same column ("after March and before June") belong in a range or dateRange variant, or in a custom filterFn.
  • Filters combine with AND. There is no OR builder across columns; express that on the server, or with a single custom filter over a computed column.
  • 'row' mode has no operator dropdown. It is one input per column by design; use 'panel' or the column menu when operators matter.

API

  • DataTableProps: enableColumnFilters, enableFilters, filterDisplayMode, filterApplyMode, filterDebounceMs, filterMode, filterFns, showActiveFilterChips, datePresets, onColumnFiltersChange.
  • ColumnDef: enableColumnFilter, filterVariant, filterFn, filterOperators, filterOptions, filterPlaceholder, filterServerKey, renderFilter, multiSelectDisplay.
  • TableInstance: setColumnFilters, resetColumnFilters, clearAllFilters, getActiveFilterCount.