Skip to main content

Filtering

You have 3,000 support tickets and need to find the ones assigned to Alice that were opened last week. Click the filter icon on "Assignee", pick Alice. Click the filter on "Created Date", set the range. Done — two clicks, no code.

Click the filter icon in a column header to filter
Live
Framework-Specific Styling

The demo uses React Radix styling. Each framework renders filter UI with its native components — dropdowns, date pickers, and inputs that match your design system.

Add filters to any column

Set filterable on a column definition. The type field picks the filter UI:

import { OGrid } from '@alaarab/ogrid-react-radix';

const columns = [
{
columnId: 'name',
name: 'Name',
filterable: { type: 'text' },
},
{
columnId: 'department',
name: 'Department',
filterable: { type: 'multiSelect', filterField: 'department' },
},
{
columnId: 'status',
name: 'Status',
filterable: { type: 'multiSelect', filterField: 'status' },
},
{
columnId: 'salary',
name: 'Salary',
type: 'numeric' as const,
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
},
];

function App() {
return (
<OGrid
columns={columns}
data={people}
getRowId={(item) => item.id}
defaultPageSize={10}
/>
);
}
Switching UI libraries

Same props across all React packages — just change the import:

  • Radix (lightweight, default): from '@alaarab/ogrid-react-radix'
  • Fluent UI (Microsoft 365 / SPFx): from '@alaarab/ogrid-react-fluent' - wrap in <FluentProvider>
  • Material UI (MUI v7): from '@alaarab/ogrid-react-material' - wrap in <ThemeProvider>

Filter types

Text filter

A case-insensitive substring search. Good for names, descriptions, anything freeform:

{ columnId: 'name', name: 'Name', filterable: { type: 'text' } }

Type "john" and it matches "John Smith", "Johnny", and "Johnathan".

Multi-select filter

A checkbox list — users pick one or more values, the grid shows only matching rows. Has built-in "Select All" and "Clear All" buttons.

{
columnId: 'status',
name: 'Status',
filterable: {
type: 'multiSelect',
filterField: 'status', // field to filter on (defaults to columnId)
optionsSource: 'static', // where options come from
options: ['Active', 'Draft', 'Archived'],
},
}

Where do the options come from?

optionsSourceHow it works
(omitted)Client-side: unique values extracted from your data. Server-side: calls your API.
'static'Uses the options array you provide — great for known enums.
'api'Calls dataSource.fetchFilterOptions(field) — for dynamic option lists.
'years'Auto-generates a year list from the current year down.

Date filter

A date range picker with "From" and "To" inputs. Shows only rows where the column value falls within the range:

{
columnId: 'createdDate',
name: 'Created',
type: 'date',
filterable: { type: 'date' },
}
tip

A column with type: 'date' automatically gets date formatting, chronological sorting, and a native date input editor — you don't need to configure those separately.

People filter

A search-as-you-type lookup that hits your directory API. Designed for Microsoft Graph, LDAP, or any endpoint that returns users:

{
columnId: 'assignee',
name: 'Assignee',
filterable: { type: 'people' },
}
info

The people filter requires a dataSource with a searchPeople method that returns UserLike objects (name, email, optional photo).

Keeping filter state outside the grid

By default the grid owns its filter state — filters reset when the component unmounts. To persist them (URL sync, page reload survival), pass filters and onFiltersChange:

function App() {
const [filters, setFilters] = useState<IFilters>({
status: { type: 'multiSelect', value: ['Active'] },
name: { type: 'text', value: 'John' },
});

return (
<OGrid
columns={columns}
data={tasks}
getRowId={(item) => item.id}
filters={filters}
onFiltersChange={setFilters}
/>
);
}

Now you can serialize filters to the URL, localStorage, or a server — and the grid will restore the exact filter state when the user comes back.

Server-side filtering

When you're using a dataSource, the grid hands off filter values to your fetchPage() function. You decide what to do with them — query params, request body, whatever your API expects:

const dataSource: IDataSource<Task> = {
fetchPage: async ({ filters, page, pageSize, sort }) => {
const params = new URLSearchParams({
page: String(page),
pageSize: String(pageSize),
});

if (filters.status?.type === 'multiSelect') {
params.set('status', filters.status.value.join(','));
}
if (filters.name?.type === 'text') {
params.set('search', filters.name.value);
}

const res = await fetch(`/api/tasks?${params}`);
return res.json(); // { items: Task[], totalCount: number }
},

// For 'api' option source on multiSelect filters:
fetchFilterOptions: async (field) => {
const res = await fetch(`/api/tasks/filter-options/${field}`);
return res.json(); // string[]
},

// For people filters:
searchPeople: async (query) => {
const res = await fetch(`/api/users/search?q=${query}`);
return res.json(); // UserLike[]
},
};
Pro tip

Server-side filtering pairs naturally with pagination. The totalCount in your fetchPage response tells the grid how many pages there are — so pagination stays accurate even when filters narrow the result set.

Props reference

PropTypeDefaultDescription
filterableIColumnFilterDefSet on IColumnDef. Enables the filter icon and defines the filter UI.
filterable.type'text' | 'multiSelect' | 'people' | 'date'Which filter UI to show.
filterable.filterFieldstringcolumnIdData field to filter on (useful when column key differs from data key).
filterable.optionsSource'api' | 'static' | 'years'autoWhere multi-select options come from.
filterable.optionsstring[]Static option list for 'static' source.
filterable.yearsCountnumberHow many years to generate for 'years' source.
filtersIFiltersControlled filter state (pass to own state externally).
onFiltersChange(filters: IFilters) => voidCalled whenever filters change.

Next steps

  • Sorting — sort filtered results by any column
  • Pagination — paginate filtered results server-side or client-side
  • Server-Side Data — move all filtering, sorting, and paging to the server