Filter.Root

The whole filtering surface of a list: trigger, editor, chips and result count.

128 results
const [values, setValues] = useState<FilterValues>({});
 
<Filter.Root
	definitions={definitions}
	values={values}
	onValuesChange={setValues}
	searchValue={search}
	onSearchChange={setSearch}
	resultCount={total}
/>;

FilterValues never holds an empty filter, so Object.keys(values).length is the active count and the record round-trips to a query string unchanged.

API

Filter.Root

PropTypeDefault
definitionsreadonly FilterDefinition[]requiredThe filters this screen offers, in the order the editor shows them. The same array drives the editor, the chips and your own query layer.
onValuesChange(values: Readonly<Record<string, FilterValue>>) => voidrequiredCalled with the new applied record: once when the editor is applied, and immediately when a chip is removed. Drafts inside the editor do not reach it, so this is safe to use as a fetch key.
valuesReadonly<Record<string, FilterValue>>requiredThe applied filters, keyed by definition.key. Never holds an empty filter — clearing one deletes its key — so Object.keys(values).length is the active count and the record round-trips to a query string unchanged.
labelsPartial<FilterLabels>Overrides for the strings the surface shows. Merged over defaultFilterLabels (English), so a partial is enough.
localestring"en-US"BCP 47 tag used to format the numbers, money and dates in the chips and the editor.
onSearchChange((value: string) => void)Called on every keystroke, unlike onValuesChange. Debounce your query.
resultCountnumberHow many rows the current filters matched, shown next to the trigger. Omit it while the count is unknown rather than passing 0.
searchValuestringOmit both search props for a list that filters without free text.

Plus every <div> prop. Source: filter/components/filter-bar.tsx.

onValuesChange fires on apply and on chip removal, never on a keystroke inside the editor, so it is safe to use directly as a fetch key. onSearchChange is the opposite — it fires per keystroke, so debounce that one yourself.

Filter.Panel

PropTypeDefault
definitionsreadonly FilterDefinition[]requiredThe filters to edit, in the order they are rendered.
labelsFilterLabelsrequiredEvery string the overlay shows, including the apply and clear buttons. Filter.Root supplies it; standalone, pass defaultFilterLabels.
localestringrequiredBCP 47 tag used to format the numbers, money and dates in the fields.
onOpenChange(open: boolean) => voidrequiredCalled when the overlay is dismissed or applied. Dismissing discards the draft.
onValuesChange(values: Readonly<Record<string, FilterValue>>) => voidrequiredCalled once, on apply, with the committed record.
openbooleanrequiredWhether the overlay is open. Controlled; pair it with onOpenChange.
valuesReadonly<Record<string, FilterValue>>requiredThe applied record the draft starts from, re-read each time the panel opens.
onSearchChange((value: string) => void)Called on every keystroke — search is not part of the apply-on-commit draft.
searchValuestringFree-text search, kept beside the filters when the list has one.

Plus the DOM props of the element it renders. Source: filter/components/filter-panel.tsx.

The overlay Filter.Root opens, exposed for the case where you want Root's draft-then-apply behaviour behind a trigger of your own. Pair it with Filter.Trigger, or with any button you like — it only needs open and onOpenChange. For the editor with no overlay and no commit step, use Filter.Form.