Customizing filter UI
Use this page when the default filtering behavior is right, but the filtering surface still needs to feel more like your product.
The safest path is to customize the existing filter experience first, then replace deeper UI only when the shipped surface no longer matches the workflow.
Show the toolbar filter button, searchable filter list, text-list/date submenus, and applied chips using realistic product wording.
Start by separating filter behavior from filter UI
A useful boundary is:
- the column contract decides which fields are filterable and what filter types they use
- the filter UI decides how those filters are presented to the user
- the backend or local row model decides how filter payloads actually affect results
That means many product-specific filter changes do not require replacing core filtering logic.
Customize the names users see first
The filter picker uses the column's filter-facing name before it falls back to the header.
That makes meta.filterName the first place to tune the labels shown in the interface.
const columns: DatatableColumn<Customer>[] = [
{
id: "owner",
accessorKey: "ownerName",
header: "Owner",
filterType: "text-list",
meta: {
filterName: "Account owner",
},
filterOptions: {
options: owners.map((owner) => ({ value: owner.id, label: owner.name })),
},
},
]Use this when the visible header is short but the filter picker needs clearer task-oriented language.
Use filter icons and option labels to match product language
The built-in filter list also supports column-level icon and label customization through metadata and options.
{
id: "status",
accessorKey: "status",
header: "Status",
filterType: "text-list",
meta: {
filterName: "Customer status",
filterIcon: StatusIcon,
},
filterOptions: {
options: [
{ value: "active", label: "Active customer" },
{ value: "trial", label: "Trial account" },
{ value: "paused", label: "Paused" },
],
},
}This is usually enough to make the filter surface feel product-aware without touching the underlying components.
For option-list filters, option rows can also render custom React. Use filterOptions.renderOption when a plain label is not enough:
{
id: "owner",
accessorFn: (row) => row.owner.name,
header: "Owner",
filterType: "text-list",
filterOptions: {
options: owners.map((owner) => ({ value: owner.name, label: owner.name })),
renderOption: (option) => {
const owner = owners.find((item) => item.name === option.value)
return (
<>
{owner ? <OwnerAvatar owner={owner} /> : null}
<span className="truncate">{option.label}</span>
</>
)
},
},
}This only changes option presentation. Filtering still uses the selected option values. renderOption is available for text-list, id-list, and boolean filters.
Pick filter types that produce the UI you want
The built-in filter picker currently branches by filterType.
In practice that means:
textandnumberopen through the modal editor flowtext-listanddateopen through inline submenu editors- only implemented filter types appear in the searchable filter list
If you want a compact pick-list experience, text-list is often the best fit. If you want freeform matching, use text. If you want range or comparison editing, use number or date.
The UI shape is part of the filter-type choice, not just the backend payload choice.
Keep the toolbar entry point aligned with the table's scope
The toolbar's filter button is the main entry point for column filtering.
<Datatable
tableKey="customers"
columns={columns}
data={rows}
toolbar={{
filterButton: true,
appliedState: {
showFilters: true,
},
}}
/>If you turn on filtering, leave applied chips visible unless the table is intentionally minimalist. The built-in experience works best when users can both open filters and see active filters in the same surface.
Use applied chips as part of the UI design, not an afterthought
Filter UI includes both the editor and the visible state it leaves behind.
The applied-state bar is where users confirm, remove, and compare active filters over time.
That means filter customization should include:
- clear filter names
- human-readable option labels
- a toolbar layout that leaves enough room for the applied-state bar to matter
If the filter picker is polished but the applied chips are confusing, the experience still feels unfinished.
Use the shipped UI when it is enough
The default filter UI is a good fit when you need to customize:
- filter labels
- icons
- option-list labels
- option-list rows with
renderOption - which columns appear in the picker
- whether filter chips are visible
In those cases, stay within the built-in surface. It is cheaper, more consistent, and easier to maintain.
Move from configuration to replacement when you need to
You are probably beyond simple configuration when the product needs:
- a radically different filter workflow than the searchable picker
- custom editors for filter types the built-in UI does not implement
- filter controls embedded somewhere other than the toolbar/applied-state pattern
- a domain-specific interaction model that does not map cleanly to the current modal/submenu flows
At that point, you are designing a different filtering product surface.
Be honest about partially implemented filter types
The type system includes custom, but the built-in UI is strongest for:
texttext-listnumberdatebooleanid-list
Treat custom as an extension seam unless you are also building the editor experience yourself.
Use this customization ladder
When filter UI needs work, move in this order:
- choose better
filterTypevalues - improve
meta.filterName,meta.filterIcon, and option labels - tune which columns are filterable at all
- keep applied chips visible and understandable
- only then consider replacing deeper UI components
That sequence usually gets the product where it needs to go with much less complexity.
Verify the filter surface in context
Before you call the filter UI done, check that:
- the filter picker uses the words your users expect
- list-style filters have clear human labels
- the modal versus submenu behavior matches the kind of question each filter asks
- active chips are understandable after the picker closes
- the UI does not imply support for filter types you have not actually implemented
Where to go next
- For adding filter behavior itself, read Add column filters.
- For the surrounding surface where the filter button lives, read Table anatomy.
- For the broader customization layer map, read Customization overview.