Filtering
One filter config per column, shown on every filter surface; quick filter; the query builder.
Every filter surface reads the same per-column config, filterDef, and shows the same editor:
- Header funnel: appears on hover, stays visible in the accent colour while the column is filtered, and opens the column's filter popover.
- Header menu: the column menu's Filter section.
- Floating filter row (
floatingFilters): compact editors under the headers. Typed values filter inline; lists, trees and ranges show a summary chip that opens the full editor. - Sidebar Filters panel: every filterable column, its filter summarized when collapsed.
- Query builder (sidebar Query panel): each condition picks a column and edits it with that column's editor.
- Filter chips (
filterChips): one per active filter; clicking a chip opens its popover.
<Grid rows={rows} columns={columns} filterChips floatingFilters />Choosing a column's filter
Column types bring a filter that fits them: a select column filters by its options (row
counts beside each), a multi-select by any / all / none of its values, a checkbox by Yes / No, a date range by overlap, a
cascading select by tree. Set filterDef on a column to choose one yourself; without either a column filters as text.
filterDef.type | Editor | Condition |
|---|---|---|
text | Operator (contains, is, starts with…, is empty) + value; optional second condition (And / Or) | { type: 'text', operator, value } |
number | Operator (=, ≥, between…) + value(s); stars: 5 shows a star picker | { type: 'number', operator, value, valueTo } |
date | On, before, after, between (with a calendar); in the last / next N days, weeks…; a period (this week…) | { type: 'date', operator, dateFrom, dateTo } |
select | Searchable checklist with counts, Select all / Clear, “(Blanks)”; listValues adds Any / All / None of | { type: 'select', values, matchMode } |
boolean | All · Yes · No | { type: 'boolean', value } |
dateRange | Overlaps / within / includes a date | { type: 'dateRange', operator, dateFrom, dateTo } |
path | Tree checklist; a node covers everything under it | { type: 'path', paths } |
custom | Your editor | anything (often a built-in condition) |
none | No filter | — |
const columns: ColumnDef<Person>[] = [
{ field: 'team', header: 'Team', filterDef: { type: 'select', options: [{ value: 'compute', label: 'Compute', color: 'blue' }, { value: 'flight', label: 'Flight' }] } },
// Without options, the choices are the column's distinct values with row counts.
{ field: 'region', header: 'Region', filterDef: { type: 'select' } },
{ field: 'tags', header: 'Tags', filterDef: { type: 'select', listValues: true } },
{ field: 'salary', header: 'Salary', filterDef: { type: 'number', format: { format: 'currency', currency: 'USD' } } },
];Server-loaded lists wait debounceMs after typing (250 by default, through the grid's scheduler), search only past
minQueryLength, and keep one request in flight: a newer search waits for it, then the latest one runs. Typed text and
number filters apply after a short pause, and at once on Enter.
Relative dates
Date filters can roll with the calendar: they are stored relative, so a saved view or a persisted filter keeps meaning "the last 30 days" tomorrow. Cells match by local calendar day, today included.
api.setState({
filter: {
columns: {
created: { type: 'date', operator: 'inLast', dateFrom: '', amount: 30, unit: 'day' }, // 'day' | 'week' | 'month' | 'year'
due: { type: 'date', operator: 'period', dateFrom: '', period: 'thisWeek' }, // today, lastMonth, thisQuarter, lastYear…
},
},
});"This week" starts on Monday; set filterDef: { type: 'date', weekStartsOn: 0 } for Sunday.
One filter: columns, query, lenses and search
The grid's filter is one piece of state, filter, with parts that a row must all pass:
columns: each column's own filter (the header funnel, floating filters, the Filters panel);query: the query builder's conditions;lenses: narrowings the person turns on over whatever view is open (the workspace's chips),{ id, label, filter };search: one text matched across fields (the workspace search box),{ text, fields? }.
setState({ filter }) changes the parts you give and keeps the others (lenses is replaced whole); filter: null
clears them all. A server or infinite datasource receives the same filter with each request, lenses included: AND
each lens with the rest.
Lenses narrow, never replace. A view's columns and query are its own; a lens is ANDed with them, so a view
keeping status = confirmed and a lens status = draft match nothing (getStatus().lenses says the view narrows the
same field, and the workspace says why). Lenses stay on as views switch, are kept in the person's record with the
search, and are not part of a view: Save keeps them out, and api.views.saveAs(name, { includeLenses: true }) folds
them into the new view's filter. A lens naming a field the grid does not have is inactive, with the reason; one on a
field a view hides still narrows.
api.setState({ filter: { lenses: [{ id: 'mine', label: 'Mine', filter: { buyer: { type: 'select', values: ['018'] } } }] } });
const [mine] = api.getStatus().lenses; // { id, label, active, reason, alsoInView }api.setState({ filter: { search: { text: 'nvda', fields: ['symbol', 'name'] } } }); // these fields
api.setState({ filter: { search: { text: 'nvda' } } }); // every field
const { columns, search, query } = api.getState().filter; // null where unset
api.setState({ filter: null }); // clear everythingReading and setting filter state
api.setState({
filter: {
columns: {
team: { type: 'select', values: ['compute', 'flight'] },
tags: { type: 'select', values: ['urgent'], matchMode: 'all' },
salary: { type: 'number', operator: 'gte', value: 100000 },
},
},
});
api.setState({ filter: { columns: { salary: null } } }); // remove one; team and tags stay
api.setState({ filter: { columns: null } }); // clear allcolumns is a patch by field: the fields you give change, null removes one, and the rest stay. To set the whole map
at once, clear it first (filter: { columns: null }) or replace the whole filter (filter: null, then set it). A saved
layout or view applied replaces the column filters exactly.
Filters are plain objects, so they persist with the grid state and saved views, and server-side row models receive them as they are.
Query builder
The query model combines conditions across columns in nested AND / OR groups. Each condition holds a column and one of the filter conditions above, matched by the same code as column filters:
api.setState({
filter: {
query: {
version: 1,
root: {
kind: 'group',
id: 'root',
operator: 'or',
children: [
{ kind: 'condition', id: 'a', columnId: 'team', filter: { type: 'select', values: ['compute'] } },
{ kind: 'condition', id: 'b', columnId: 'salary', filter: { type: 'number', operator: 'gt', value: 200000 } },
],
},
},
},
});Following the filters
The filters are state: follow them with watch.
api.watch(
(grid) => grid.filter.columns,
(columns) => console.log(columns)
);
api.watch(
(grid) => grid.filter.search,
(quickFilter) => console.log(quickFilter?.text)
);Last updated on