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
| Prop | Type | Default | |
|---|---|---|---|
definitions | readonly FilterDefinition[] | required | The 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>>) => void | required | Called 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. |
values | Readonly<Record<string, FilterValue>> | required | The 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. |
labels | Partial<FilterLabels> | — | Overrides for the strings the surface shows. Merged over
defaultFilterLabels (English), so a partial is enough. |
locale | string | "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. |
resultCount | number | — | How many rows the current filters matched, shown next to the trigger. Omit
it while the count is unknown rather than passing 0. |
searchValue | string | — | Omit 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
| Prop | Type | Default | |
|---|---|---|---|
definitions | readonly FilterDefinition[] | required | The filters to edit, in the order they are rendered. |
labels | FilterLabels | required | Every string the overlay shows, including the apply and clear buttons.
Filter.Root supplies it; standalone, pass defaultFilterLabels. |
locale | string | required | BCP 47 tag used to format the numbers, money and dates in the fields. |
onOpenChange | (open: boolean) => void | required | Called when the overlay is dismissed or applied. Dismissing discards the draft. |
onValuesChange | (values: Readonly<Record<string, FilterValue>>) => void | required | Called once, on apply, with the committed record. |
open | boolean | required | Whether the overlay is open. Controlled; pair it with onOpenChange. |
values | Readonly<Record<string, FilterValue>> | required | The 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. |
searchValue | string | — | Free-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.