Filter.Root

The whole filtering surface of a list: search, 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.

Two ways to commit

The default, mode="apply", puts a search-shaped trigger in the bar and opens a dialog whose edits are a draft: nothing reaches onValuesChange until Apply, and dismissing discards. Pick it when every change costs a request you would rather not make twice.

mode="live" puts a real search input in the bar beside a "Filters" button, and the side sheet behind it applies each change as it happens — a tap at once, typing after a 300 ms pause — so the sheet can stay open while the rows shift behind it. Pick it for an admin list, where narrowing and reading alternate.

128 results
<Filter.Root
	mode="live"
	definitions={definitions}
	values={values}
	onValuesChange={setValues}
	searchValue={search}
	onSearchChange={setSearch}
	resultCount={total}
/>;

For a rail beside the list instead of an overlay, keep the bar and render Filter.Dock yourself, with panelRenderedElsewhere.

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, and immediately when a chip is removed. In apply mode once per Apply — drafts inside the editor never reach it; in live mode as the filters change, typing after a 300 ms pause. Either way it 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.
fullWidthTriggerbooleanfalseLet the trigger (or the search input) fill the row instead of capping at max-w-md. The cap suits wide tables; a narrow content column wants the bar flush with the cards below it.
hideChipsbooleanfalseDrop the chips row. For filters that are standing preferences rather than ad-hoc facets: the active count on the trigger already says they apply, and chips would restate them forever.
labelsPartial<FilterLabels>—Overrides for the strings the surface shows. Merged over defaultFilterLabels (English), so a partial is enough.
localestring—BCP 47 tag used to format the numbers, money and dates in the chips and the editor.
mode"apply" | "live""apply"How the editor commits. apply (the default): a search-field-shaped trigger opens a dialog whose edits are a draft committed on Apply, search included. live: a real search input sits in the bar beside a "Filters" button, and the side sheet behind it applies each change as it happens.
onOpenChange((open: boolean) => void)—
onSearchChange((value: string) => void)—Called on every keystroke (in apply mode, on Apply). Debounce your query.
openboolean—Drive the editor from outside, for a screen with a second way in (an empty state whose call to action is "adjust your filters"). Omit both to let the bar own the state.
panelRenderedElsewherebooleanfalseLeave the editor to the caller: a list that docks Filter.Dock beside its table keeps only the bar here, and the bar's button toggles the rail through open/onOpenChange — so the two never both draw the fields.
resultCountnumber—How many rows the current filters matched, shown next to the trigger. Omit it while the count is unknown rather than passing 0.
searchValuestring—Omit both search props for a list that filters without free text.
exitExit—Inside a <Presence>: plays this child's way out before it unmounts.

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

onValuesChange fires on apply (or, live, once the edit settles) and on chip removal, never on a keystroke inside the editor, so it is safe to use directly as a fetch key. onSearchChange fires per keystroke in live mode, so debounce that one yourself.

hideChips drops the chips row for filters that are standing preferences rather than ad-hoc facets; open and onOpenChange let an empty state's "adjust your filters" button open the same editor.

Filter.Panel

PropTypeDefault
definitionsreadonly FilterDefinition[]requiredThe filters to edit, in the order they are rendered.
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.
labelsPartial<FilterLabels>—Overrides for the strings the overlay shows, including the apply and clear buttons, merged over the surrounding filter root's (English defaults at the outermost level).
localestring—BCP 47 tag used to format the numbers, money and dates; inherited when omitted.
onSearchChange((value: string) => void)—Called on every keystroke — search is not part of the apply-on-commit draft.
searchValuestring—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 dialog mode="apply" opens, exposed for the case where you want its 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.

Filter.Sheet

PropTypeDefault
definitionsreadonly FilterDefinition[]requiredThe filters to edit, in the order they are rendered.
onOpenChange(open: boolean) => voidrequiredCalled when the sheet is dismissed or closed from its footer. Edits are already applied.
onValuesChange(values: Readonly<Record<string, FilterValue>>) => voidrequiredCalled as the filters change: at once for a tap (an option, a preset, a place), after a 300 ms pause for typing. Safe to use as a fetch key.
openbooleanrequiredWhether the sheet is open. Controlled; pair it with onOpenChange.
valuesReadonly<Record<string, FilterValue>>requiredThe applied record. Changes made elsewhere (a removed chip) flow back in.
labelsPartial<FilterLabels>—Overrides for the strings the sheet shows, merged over the surrounding filter root's (English defaults at the outermost level).
localestring—BCP 47 tag used to format the numbers, money and dates; inherited when omitted.

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

The side sheet (a bottom drawer on mobile) mode="live" opens. Edits apply as they happen, so its footer has Close rather than Apply. From six filters on, a search box at the top finds one by name, accent- and case-blind.