Filters
Build rich, shareable filter bars for data-heavy interfaces.
Overview
The Filters composite lets teams surface complex filtering logic through a configurable, accessible UI. Define fields once, then let the component handle operator selection, value inputs, grouped layouts, and optional URL synchronisation.
By default, each field can only be added once. Set allowMultiple when you want repeated filters for the same field. When filters are active, the add menu also includes a built-in Clear all action.
Usage
import {
Filters,
defineFilters,
useFiltersState,
useFiltersChangeHandler,
} from "@tilt-legal/cubitt-components/composites";const filterDefs = defineFilters([
{
key: "status",
label: "Status",
type: "select",
options: [
{ value: "todo", label: "To Do" },
{ value: "done", label: "Done" },
],
},
{
key: "assignee",
label: "Assignee",
type: "multiselect",
options: [
{ value: "john", label: "John" },
{ value: "jane", label: "Jane" },
],
},
] as const);
const [filters, setFilters] = useFiltersState<typeof filterDefs>([]);
const handleFiltersChange = useFiltersChangeHandler<typeof filterDefs>(setFilters);
return <Filters filters={filters} fields={filterDefs.fields} onChange={handleFiltersChange} />;Build a filterDefs array—flat or grouped definitions of every field you want to expose. Use it to declare every filterable field once and unlock type-safe helpers like createFilter.
const teamMembers = [
{ value: "john", label: "John Doe" },
{ value: "jane", label: "Jane Smith" },
] as const;
const filterDefs = defineFilters([
{
group: "Details",
fields: [
{
key: "status",
label: "Status",
type: "select",
options: [
{ value: "todo", label: "To Do" },
{ value: "done", label: "Done" },
],
},
{
key: "assignee",
label: "Assignee",
type: "multiselect",
maxSelections: 3,
options: teamMembers,
},
],
},
{
key: "createdAt",
label: "Created",
type: "date",
},
] as const);The returned object contains:
fields– the normalized array passed to theFilterscomponent.filterKeys– list of validfieldidentifiers.createFilter(field, operator, values, options?)– a typed factory that builds filters matching your definition (used in programmatic presets). Refer to the API Reference below for the full list of field properties.
Persist filters in the query string by providing paramName:
<Filters
filters={filters}
fields={filterDefs.fields}
onChange={handleFiltersChange}
paramName="filters"
paramHistory="replace"
paramDebounce={200}
/>Want URL-backed filters with shareable links and reload persistence? Continue below for the dedicated URL sync example.
Examples
Custom Trigger
"use client";
import {
Avatar,
AvatarFallback,
AvatarImage,
Button,
} from "@tilt-legal/cubitt-components/primitives";
import {
defineFilters,
Filters,
useFiltersChangeHandler,
useFiltersState,
} from "@tilt-legal/cubitt-components/composites";
import {
Calendar,
Check,
Envelope,
Filter2Plus,
Globe,
Star,
Tag,
User,
Xmark,
} from "@tilt-legal/cubitt-icons/ui/outline";
const PriorityIcon = ({ priority }: { priority: string }) => {
const colors = {
low: "text-green-500",
medium: "text-yellow-500",
high: "text-orange-500",
urgent: "text-red-500",
};
return <Star className={colors[priority as keyof typeof colors]} />;
};
export function FiltersCustomAddButtonExample() {
const filterDefs = defineFilters([
{
key: "text",
label: "Text",
icon: <Tag className="size-3.5" />,
type: "text",
className: "w-36",
placeholder: "Search text...",
},
{
key: "email",
label: "Email",
icon: <Envelope className="size-3.5" />,
type: "email",
className: "w-48",
placeholder: "user@example.com",
},
{
key: "website",
label: "Website",
icon: <Globe className="size-3.5" />,
type: "url",
className: "w-40",
placeholder: "https://example.com",
},
{
key: "assignee",
label: "Assignee",
icon: <User className="size-3.5" />,
type: "multiselect",
className: "w-[200px]",
options: [
{
value: "john",
label: "John Doe",
icon: (
<Avatar className="size-5">
<AvatarImage alt="John Doe" src="https://randomuser.me/api/portraits/men/1.jpg" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
),
},
{
value: "jane",
label: "Jane Smith",
icon: (
<Avatar className="size-5">
<AvatarImage alt="Jane Smith" src="https://randomuser.me/api/portraits/women/2.jpg" />
<AvatarFallback>JS</AvatarFallback>
</Avatar>
),
},
{
value: "bob",
label: "Bob Johnson",
icon: (
<Avatar className="size-5">
<AvatarImage alt="Bob Johnson" src="https://randomuser.me/api/portraits/men/3.jpg" />
<AvatarFallback>BJ</AvatarFallback>
</Avatar>
),
},
{
value: "alice",
label: "Alice Brown",
icon: (
<Avatar className="size-5">
<AvatarImage
alt="Alice Brown"
src="https://randomuser.me/api/portraits/women/4.jpg"
/>
<AvatarFallback>AB</AvatarFallback>
</Avatar>
),
},
{
value: "nick",
label: "Nick Bold",
icon: (
<Avatar className="size-5">
<AvatarImage alt="Nick Bold" src="https://randomuser.me/api/portraits/men/4.jpg" />
<AvatarFallback>NB</AvatarFallback>
</Avatar>
),
},
],
},
{
key: "priority",
label: "Priority",
icon: <Star className="size-3.5" />,
type: "multiselect",
className: "w-[180px]",
options: [
{ value: "low", label: "Low", icon: <PriorityIcon priority="low" /> },
{
value: "medium",
label: "Medium",
icon: <PriorityIcon priority="medium" />,
},
{
value: "high",
label: "High",
icon: <PriorityIcon priority="high" />,
},
{
value: "urgent",
label: "Urgent",
icon: <PriorityIcon priority="urgent" />,
},
],
},
{
key: "dueDate",
label: "Due Date",
icon: <Calendar className="size-3.5" />,
type: "date",
className: "w-36",
},
{
key: "score",
label: "Score",
icon: <Star className="size-3.5" />,
type: "number",
min: 0,
max: 100,
step: 1,
},
{
key: "isActive",
label: "Active Status",
icon: <Check className="size-3.5" />,
type: "boolean",
},
] as const);
const [filters, setFilters] = useFiltersState<typeof filterDefs>([
filterDefs.createFilter("assignee", "is_any_of", ["john", "nick", "alice"], {
id: "filters-custom-add-button-assignee",
}),
]);
const handleFiltersChange = useFiltersChangeHandler<typeof filterDefs>(setFilters);
return (
<div className="space-y-4">
<Filters
className="justify-center"
fields={filterDefs.fields}
filters={filters}
onChange={handleFiltersChange}
trigger={
<Button mode="icon">
<Filter2Plus />
</Button>
}
/>
{filters.length > 0 && (
<Button onClick={() => setFilters([])} variant="secondary">
<Xmark /> Clear
</Button>
)}
</div>
);
}Validation
Try a non-@tilt.legal address, then blur the field to see the custom
validation message.
const filterDefs = defineFilters([
{
key: "email",
label: "Email",
type: "email",
validation: (value) => {
const email = String(value).trim().toLowerCase();
if (!email.endsWith("@tilt.legal")) {
// Returning an object overrides the default fallback validation message.
return {
valid: false,
message: "Use a @tilt.legal address for this filter.",
};
}
return true;
},
},
] as const);Data Table Integration
Use Filters to power the rows rendered by DataTable.
"use client";
import { Badge } from "@tilt-legal/cubitt-components/primitives";
import {
DataTable,
defineFilters,
Filters,
useFiltersTanstack,
} from "@tilt-legal/cubitt-components/composites";
import type { ColumnDef } from "@tanstack/react-table";
const employees = [
{
id: "1",
name: "Alice Johnson",
email: "alice@example.com",
status: "Active",
department: "Engineering",
location: "New York",
},
{
id: "2",
name: "Michael Chen",
email: "michael@example.com",
status: "Active",
department: "Design",
location: "San Francisco",
},
{
id: "3",
name: "Priya Nair",
email: "priya@example.com",
status: "On Leave",
department: "Marketing",
location: "Toronto",
},
{
id: "4",
name: "Daniel Martinez",
email: "daniel@example.com",
status: "Inactive",
department: "Sales",
location: "Mexico City",
},
{
id: "5",
name: "Fatima Al-Sayed",
email: "fatima@example.com",
status: "Active",
department: "Product",
location: "Dubai",
},
];
type Employee = (typeof employees)[number];
const filterDefs = defineFilters([
{
key: "search",
type: "text",
label: "Search",
placeholder: "Search name, email, or department",
operators: [{ value: "contains", label: "Contains" }],
},
{
key: "status",
type: "select",
label: "Status",
operators: [
{ value: "is", label: "Is" },
{ value: "is_not", label: "Is not" },
],
},
{
key: "department",
type: "select",
label: "Department",
operators: [{ value: "is", label: "Is" }],
},
]);
const baseColumns: ColumnDef<Employee>[] = [
{
accessorKey: "name",
header: "Name",
cell: ({ row }) => (
<div className="font-medium text-fg-1">{row.getValue("name") as string}</div>
),
},
{
accessorKey: "email",
header: "Email",
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
const status = row.getValue("status") as string;
const variant =
status === "Active" ? "success" : status === "On Leave" ? "secondary" : "destructive";
return <Badge variant={variant}>{status}</Badge>;
},
},
{
accessorKey: "department",
header: "Department",
},
{
accessorKey: "location",
header: "Location",
},
];
export function FiltersDataTableIntegrationExample() {
const {
filters,
handleFiltersChange,
filterFields,
columns,
columnFilters,
onColumnFiltersChange,
} = useFiltersTanstack<Employee, typeof filterDefs>({
definition: filterDefs,
columns: baseColumns,
data: employees,
fieldColumnMap: { search: "name" },
searchResolvers: {
search: (row) => [row.name, row.email, row.department],
},
});
return (
<div className="space-y-4">
<Filters fields={filterFields} filters={filters} onChange={handleFiltersChange} />
<DataTable
columnFilters={columnFilters}
columns={columns}
data={employees}
enableFiltering
enableSorting
onColumnFiltersChange={onColumnFiltersChange}
/>
</div>
);
}Size Variants
"use client";
import { Button } from "@tilt-legal/cubitt-components/primitives";
import {
defineFilters,
Filters,
useFiltersChangeHandler,
useFiltersState,
} from "@tilt-legal/cubitt-components/composites";
import {
Ban,
Calendar,
Check,
Clock,
Envelope,
Globe,
Plus,
Star,
Tag,
} from "@tilt-legal/cubitt-icons/ui/outline";
const PriorityIcon = ({ priority }: { priority: string }) => {
const colors = {
low: "text-green-500",
medium: "text-yellow-500",
high: "text-orange-500",
urgent: "text-red-500",
};
return <Star className={colors[priority as keyof typeof colors]} />;
};
export function FiltersSizeExample() {
const filterDefs = defineFilters([
{
key: "text",
label: "Text",
icon: <Tag className="size-3.5" />,
type: "text",
className: "w-36",
placeholder: "Search text...",
},
{
key: "email",
label: "Email",
icon: <Envelope className="size-3.5" />,
type: "email",
className: "w-48",
placeholder: "user@example.com",
},
{
key: "website",
label: "Website",
icon: <Globe className="size-3.5" />,
type: "url",
className: "w-40",
placeholder: "https://example.com",
},
{
key: "status",
label: "Status",
icon: <Clock className="size-3.5" />,
type: "select",
searchable: false,
className: "w-[200px]",
options: [
{
value: "todo",
label: "To Do",
icon: <Clock className="size-3 text-brand-1" />,
},
{
value: "in-progress",
label: "In Progress",
icon: <Clock className="size-3 text-yellow-500" />,
},
{
value: "done",
label: "Done",
icon: <Check className="size-3 text-green-500" />,
},
{
value: "cancelled",
label: "Cancelled",
icon: <Ban className="size-3 text-destructive" />,
},
],
},
{
key: "priority",
label: "Priority",
icon: <Star className="size-3.5" />,
type: "multiselect",
className: "w-[180px]",
options: [
{ value: "low", label: "Low", icon: <PriorityIcon priority="low" /> },
{
value: "medium",
label: "Medium",
icon: <PriorityIcon priority="medium" />,
},
{
value: "high",
label: "High",
icon: <PriorityIcon priority="high" />,
},
{
value: "urgent",
label: "Urgent",
icon: <PriorityIcon priority="urgent" />,
},
],
},
{
key: "dueDate",
label: "Due Date",
icon: <Calendar className="size-3.5" />,
type: "date",
className: "w-36",
},
{
key: "score",
label: "Score",
icon: <Star className="size-3.5" />,
type: "number",
min: 0,
max: 100,
step: 1,
},
{
key: "isActive",
label: "Active Status",
icon: <Check className="size-3.5" />,
type: "boolean",
},
] as const);
const [smallFilters, setSmallFilters] = useFiltersState<typeof filterDefs>([
filterDefs.createFilter("priority", "is_any_of", ["high", "urgent"]),
]);
const [mediumFilters, setMediumFilters] = useFiltersState<typeof filterDefs>([
filterDefs.createFilter("dueDate", "is", ["2025-01-01"]),
]);
const [largeFilters, setLargeFilters] = useFiltersState<typeof filterDefs>([
filterDefs.createFilter("email", "contains", ["example@example.com"]),
]);
const handleSmallFiltersChange = useFiltersChangeHandler<typeof filterDefs>(setSmallFilters);
const handleMediumFiltersChange = useFiltersChangeHandler<typeof filterDefs>(setMediumFilters);
const handleLargeFiltersChange = useFiltersChangeHandler<typeof filterDefs>(setLargeFilters);
return (
<div className="flex w-full flex-col items-center justify-center gap-6">
<Filters
fields={filterDefs.fields}
filters={smallFilters}
onChange={handleSmallFiltersChange}
size="sm"
trigger={
<Button mode="icon" size="sm" variant="secondary">
<Plus />
</Button>
}
/>
<Filters
className="justify-center"
fields={filterDefs.fields}
filters={mediumFilters}
onChange={handleMediumFiltersChange}
size="md"
trigger={
<Button mode="icon" variant="secondary">
<Plus />
</Button>
}
/>
<Filters
fields={filterDefs.fields}
filters={largeFilters}
onChange={handleLargeFiltersChange}
size="lg"
trigger={
<Button mode="icon" size="lg" variant="secondary">
<Plus />
</Button>
}
/>
</div>
);
}Programmatic Presets
"use client";
import { Button } from "@tilt-legal/cubitt-components/primitives";
import {
defineFilters,
Filters,
useFiltersChangeHandler,
useFiltersState,
} from "@tilt-legal/cubitt-components/composites";
import { Bell, Sliders } from "@tilt-legal/cubitt-icons/ui/outline";
const filterDefs = defineFilters([
{
key: "status",
label: "Status",
type: "select",
icon: <Bell />,
options: [
{ value: "todo", label: "To Do" },
{ value: "in-progress", label: "In Progress" },
{ value: "done", label: "Done" },
],
},
{
key: "priority",
label: "Priority",
type: "multiselect",
icon: <Sliders />,
options: [
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High" },
],
},
] as const);
export function FiltersLocalPresetExample() {
const [filters, setFilters] = useFiltersState<typeof filterDefs>([]);
const handleFiltersChange = useFiltersChangeHandler<typeof filterDefs>(setFilters);
const applyPreset = () => {
handleFiltersChange([
filterDefs.createFilter("status", "is", ["done"], {
id: "local-preset-status-done",
}),
filterDefs.createFilter("priority", "is_any_of", ["high"], {
id: "local-preset-priority-high",
}),
]);
};
return (
<div className="space-y-4">
<div className="flex gap-1">
{filters.length === 0 && (
<Button onClick={applyPreset} variant="secondary">
Apply filter set
</Button>
)}
{filters.length > 0 && (
<Button onClick={() => setFilters([])} variant="secondary">
Clear
</Button>
)}
</div>
<Filters
className="justify-center"
fields={filterDefs.fields}
filters={filters}
onChange={handleFiltersChange}
/>
</div>
);
}URL Sync
"use client";
import { Button } from "@tilt-legal/cubitt-components/primitives";
import {
defineFilters,
Filters,
useFiltersChangeHandler,
useFiltersState,
} from "@tilt-legal/cubitt-components/composites";
import {
Ban,
Bell,
Calendar,
Check,
Clock,
Envelope,
Globe,
Phone,
Sliders,
Star,
User,
Xmark,
} from "@tilt-legal/cubitt-icons/ui/outline";
const PriorityIcon = ({ priority }: { priority: string }) => {
const colors = {
low: "bg-green-500",
medium: "bg-yellow-500",
high: "bg-violet-500",
urgent: "bg-orange-500",
critical: "bg-red-500",
};
return (
<div className={`size-2.25 shrink-0 rounded-full ${colors[priority as keyof typeof colors]}`} />
);
};
export function FiltersUrlStateExample() {
const filterDefs = defineFilters([
{
group: "Basic",
fields: [
{
key: "text",
label: "Text",
type: "text",
icon: <Envelope />,
placeholder: "Search text...",
},
{
key: "email",
label: "Email",
type: "email",
icon: <Envelope />,
placeholder: "user@example.com",
},
{
key: "website",
label: "Website",
icon: <Globe />,
type: "url",
className: "w-40",
placeholder: "https://example.com",
},
{
key: "phone",
label: "Phone",
icon: <Phone />,
type: "tel",
className: "w-40",
placeholder: "+1 (123) 456-7890",
},
{
key: "isActive",
label: "Is active ?",
icon: <Check />,
type: "boolean",
},
],
},
{
group: "Select",
fields: [
{
key: "status",
label: "Status",
icon: <Bell />,
type: "select",
searchable: false,
className: "w-[200px]",
options: [
{
value: "todo",
label: "To Do",
icon: <Clock className="size-3 text-brand-1" />,
},
{
value: "in-progress",
label: "In Progress",
icon: <Clock className="size-3 text-yellow-500" />,
},
{
value: "done",
label: "Done",
icon: <Check className="size-3 text-green-500" />,
},
{
value: "cancelled",
label: "Cancelled",
icon: <Ban className="size-3 text-destructive" />,
},
],
},
{
key: "priority",
label: "Priority",
icon: <Sliders />,
type: "multiselect",
className: "w-[180px]",
options: [
{
value: "low",
label: "Low",
icon: <PriorityIcon priority="low" />,
},
{
value: "medium",
label: "Medium",
icon: <PriorityIcon priority="medium" />,
},
{
value: "high",
label: "High",
icon: <PriorityIcon priority="high" />,
},
{
value: "urgent",
label: "Urgent",
icon: <PriorityIcon priority="urgent" />,
},
{
value: "critical",
label: "Critical",
icon: <PriorityIcon priority="critical" />,
},
],
},
{
key: "assignee",
label: "Assignee",
icon: <User />,
type: "multiselect",
maxSelections: 5,
options: [
{
value: "john",
label: "John Doe",
icon: <div className="size-3 rounded-full bg-blue-400" />,
},
{
value: "jane",
label: "Jane Smith",
icon: <div className="size-3 rounded-full bg-green-400" />,
},
{
value: "bob",
label: "Bob Johnson",
icon: <div className="size-3 rounded-full bg-purple-400" />,
},
{
value: "unassigned",
label: "Unassigned",
icon: <User className="size-3 text-gray-400" />,
},
],
},
{
key: "userType",
label: "User Type",
icon: <User />,
type: "select",
searchable: false,
className: "w-[200px]",
options: [
{
value: "premium",
label: "Premium",
icon: <Star className="size-3 text-yellow-500" />,
},
{
value: "standard",
label: "Standard",
icon: <User className="size-3 text-blue-500" />,
},
{
value: "trial",
label: "Trial",
icon: <Clock className="size-3 text-gray-500" />,
},
],
},
],
},
{
group: "Date & Time",
fields: [
{
key: "dueDate",
label: "Due Date",
icon: <Calendar />,
type: "date",
className: "w-36",
},
{
key: "orderDate",
label: "Order Date",
icon: <Calendar />,
type: "select",
searchable: false,
className: "w-[200px]",
options: [
{ value: "past", label: "in the past" },
{ value: "24h", label: "24 hours from now" },
{ value: "3d", label: "3 days from now" },
{ value: "1w", label: "1 week from now" },
{ value: "1m", label: "1 month from now" },
{ value: "3m", label: "3 months from now" },
],
},
{
key: "dateRange",
label: "Date Range",
icon: <Calendar />,
type: "daterange",
},
{
key: "createdAt",
label: "Created At",
icon: <Clock />,
type: "datetime",
},
{
key: "workingHours",
label: "Working Hours",
icon: <Clock />,
type: "time",
},
],
},
{
group: "Numbers",
fields: [
{
key: "score",
label: "Score",
icon: <Star />,
type: "number",
min: 0,
max: 100,
step: 1,
},
{
key: "salary",
label: "Salary",
icon: <Star />,
type: "number",
prefix: "$",
className: "w-24",
min: 0,
max: 500_000,
step: 1000,
},
{
key: "completion",
label: "Completion",
icon: <Check />,
className: "w-24",
suffix: "%",
type: "number",
step: 5,
},
],
},
] as const);
const [filters, setFilters] = useFiltersState<typeof filterDefs>([]);
const handleFiltersChange = useFiltersChangeHandler<typeof filterDefs>(setFilters);
return (
<div className="space-y-4">
<Filters
className="justify-center"
fields={filterDefs.fields}
filters={filters}
onChange={handleFiltersChange}
paramHistory="replace"
paramName="url-state-demo"
/>
{filters.length > 0 && (
<Button onClick={() => setFilters([])} variant="secondary">
<Xmark /> Clear
</Button>
)}
</div>
);
}Set Presets (Server-Side)
When you need to seed filters outside the UI—redirects, emails, or server-rendered pages—use createFiltersSerializer to produce a validated query string that the Filters component can hydrate from.
If your app configures a custom TanStack Router parseSearch / stringifySearch, pass those same functions into createFiltersSerializer so preset URLs match runtime routing behavior.
import { createFiltersSerializer } from "@tilt-legal/cubitt-components";
import {
DEFAULT_I18N,
createFilterArrayValidator,
defineFilters,
} from "@tilt-legal/cubitt-components/composites";
const definition = defineFilters([
{
key: "status",
label: "Status",
type: "select",
options: [
{ value: "todo", label: "To Do" },
{ value: "done", label: "Done" },
],
},
] as const);
const validator = createFilterArrayValidator(definition.fields, DEFAULT_I18N);
const serializeFilters = createFiltersSerializer(validator);
const presetUrl = serializeFilters(
[
definition.createFilter("status", "is", ["done"], {
id: "preset-status-done",
}),
],
"https://app.tilt.dev/tasks",
);
// -> https://app.tilt.dev/tasks?filters=[...]API Reference
Filters
| Prop | Type | Default | Description |
|---|---|---|---|
filters | Filter[] | [] | Controlled list rendered by the component. |
fields | FilterFieldsConfig | - | Definition produced by defineFilters. |
onChange | (filters: Filter[]) => void | - | Called whenever a filter is added, edited, or removed. |
className | string | - | Additional class names applied to the Filters wrapper. |
size | "sm" | "md" | "lg" | "md" | Chip sizing. |
i18n | Partial<FilterI18nConfig> | DEFAULT_I18N | Merge custom strings with the default copy. |
showSearchInput | boolean | true | Display the field search input in the add menu. |
trigger | React.ReactNode | - | Custom trigger element shown instead of the default add button. |
allowMultiple | boolean | false | Allow multiple filters with the same field key. |
debug | boolean | (filters: Filter[]) => void | false | Enable logging of filter changes or provide a debug hook. |
URL State Props
When paramName is provided the Filters component synchronises its value to TanStack Router search params.
| Prop | Type | Default | Description |
|---|---|---|---|
paramName | string | — | Querystring key used to persist filter state. Enables URL synchronisation. |
paramHistory | "push" | "replace" | "push" | Browser history behaviour applied to URL updates. |
paramDebounce | number | 150 | Debounce duration (ms) for URL updates (set 0 to disable). |
paramClearOnDefault | boolean | true | Remove the URL parameter when filters match the default state. |
paramSerializer | (filters: Filter[]) => Promise<void> | void | — | Optional override invoked before writing filter state to the URL. |
Filter Definitions
| Property | Type | Description |
|---|---|---|
key | string | Unique identifier for the filterable field. |
label | string | Display name shown in the UI. |
type | 'text' | 'select' | 'multiselect' | 'number' | 'date' | 'daterange' | 'boolean' | 'custom' | ... | Determines default operators and value editors. |
icon | React.ReactNode | Optional icon rendered with the field label. |
options | FilterOption[] | Required for select-style fields; each option may include value, label, icon, and metadata. |
operators | FilterOperator[] | Override the default operator list for the field. |
defaultOperator | string | Force a specific operator when the filter is first created. |
placeholder | string | Placeholder text for value inputs. |
searchable | boolean | Enables search within select / multiselect menus. |
maxSelections | number | Limit multiselect selections. |
className | string | Field-level class names applied to the rendered value control. |
min / max / step | number | Numeric input bounds and stepping for number fields. |
prefix / suffix | React.ReactNode | string | Inline adornments rendered inside text and number inputs. |
group / fields | FilterFieldGroup | Group fields under a named section. |
customRenderer | (props: CustomRendererProps) => React.ReactNode | Provide a bespoke value entry UI. |
customValueRenderer | (values, options) => React.ReactNode | Custom rendering for chip values. |
value / onValueChange | unknown[] / (values: unknown[]) => void | Opt into controlled values for a specific field. |
validation | (value: unknown) => boolean | { valid: boolean; message?: string } | Additional validation hook for user-entered values. Return an object to provide a custom error message. |
Helpers
defineFilters– create strongly typed filter definitions.createFilter– build filters manually.useFiltersState– convenience hook for local state.useFiltersChangeHandler– adapter that forwardsonChangeresults tosetState.createFilterArrayValidator– validator compatible with Filters URL-state parsing and serializer helpers.createFiltersSerializer– pure helper for generating validated preset URLs. Pass custom routerparseSearch/stringifySearchfunctions if your app overrides TanStack Router's defaults.