FilterPanel
Multi-facet checkbox filter panel. Pass a facets array describing
each facet (label + options + optional collapsibility) and the panel
renders a header with a reset action, then a labelled checkbox group
per facet. Counts surface on the right as tabular pills.
Selections are emitted as Record<facetId, readonly string[]> and
supported in both controlled (value + onValueChange) and
uncontrolled (defaultValue) modes — mirroring Slider and NavBar.
The data shape#
You pass facets (the filter groups) and optionally a counts record
mapping each option to a per-option pill count.
<FilterPanel
facets={[
{
id: 'category',
label: 'Category',
options: [
{ value: 'food', label: 'Food' },
{ value: 'travel', label: 'Travel' },
],
},
]}
defaultValue={{ category: ['food'] }}
counts={{ category: { food: 142, travel: 38 } }}
/>The selection record is keyed by facet.id; the array under each
key contains the selected option.value strings.
Default#
Three facets, two of them pre-selected. Counts illuminate the relative
size of each option's match set; clicking Reset clears the
selection and fires the optional onReset callback alongside
onValueChange({}).
Controlled#
When you need to drive the panel from URL state or another store, pass
value + onValueChange. The shape matches the defaultValue above.
const [filters, setFilters] = useState<Record<string, readonly string[]>>({});
<FilterPanel facets={…} value={filters} onValueChange={setFilters} />;Facet (FilterFacet)#
Each entry in facets.
id: string(required) — stable identifier. The key under which this facet's selections appear invalue/defaultValue.label: ReactNode(required) — group heading rendered above the checkboxes.options: ReadonlyArray<FilterFacetOption>(required) — the individual checkbox rows.collapsible?: boolean— whether the group can fold. Defaulttrue. Whenfalse, the group is always open and the disclosure caret is suppressed.defaultOpen?: boolean— initial open state for collapsible groups. Defaulttrue.
Facet option (FilterFacetOption)#
Each row inside a facet.
value: string(required) — the stable string committed toonValueChangeand stored under the facet's array.label: ReactNode(required) — visible row label.
Counts (counts)#
Per-option counts rendered as small tabular pills on the right of each
row. Shape is { [facetId]: { [optionValue]: number } }. Pass 0 for
known-empty rows; omit a value to skip the pill entirely.
Top-level props#
| Prop | Type | Default | Description |
|---|---|---|---|
| facets * | readonly FilterFacet[] | — | |
| value | FilterPanelValue | undefined | — | Controlled selection map keyed by facet id. |
| defaultValue | FilterPanelValue | undefined | — | Uncontrolled initial selection map. Default `{}`. |
| onValueChange | ((next: FilterPanelValue) => void) | undefined | — | Fires whenever the selection changes — including reset. |
| onReset | (() => void) | undefined | — | Fired when the reset action is invoked, alongside `onValueChange({})`. |
| counts | Record<string, Record<string, number>> | undefined | — | Optional per-option counts shown in a trailing pill. Shape: `{ [facetId]: { [optionValue]: number } }`. |
| title | ReactNode | Filter | Override the header title. Default `'Filter'`. |
| resetLabel | ReactNode | Reset | Override the reset button label. Default `'Reset'`. |