Filter Bar
Search, filter controls built from definitions, active-filter chips, and Apply and Reset, above a table, list or dashboard.<FilterBar filters={usersFilters} defaultValue={{ status: 'active', verified: false }} />Installation
pnpm add @advui/datanpm install @advui/dataUsage
import { FilterBar, type FilterState } from '@advui/data'
const [filters, setFilters] = useState<FilterState>({})
<FilterBar
searchable
filters={[
{
key: 'status',
label: 'Status',
type: 'select',
options: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
],
},
{ key: 'created', label: 'Created', type: 'date-range' },
]}
value={filters}
onValueChange={setFilters}
showActiveFilters
showResetButton
/>Examples
Search and a status filter
Uncontrolled: the bar keeps its own state.Filtering a data table
Controlled. The app filters the rows with the state; `ghost` sits flush above the table.Multi-select filter
Picks show as chips in the field, removable one by one; a disabled option cannot be picked.Date range with Apply and Reset
Changes are a draft until Apply. Reset puts the defaults back and applies at once.Active filters and count
Chips remove one filter each; the count and Reset follow. A Verified filter set to No counts.Dashboard filters
`layout="responsive"`: stacked on phones, a row from `md`, on a `filled` surface.Leading and trailing content
A title at the start and an Export button after the filters.Users
Advanced filters
Filters marked `advanced` wait behind "More filters", which counts the active ones; closing keeps their values.Custom filter control
A Number Input as a typed `custom` filter, with its own chip text.Compact, without labels
`showLabels={false}` and `size="sm"`: one line, the labels name the controls.Variants
`default`, `outline`, `filled` and `ghost` style the container only.API reference
FilterBar
Also takes Stack style props (`width`, `margin`…), which win over the variant and size.| Prop | Type | Default | Description |
|---|---|---|---|
filters | FilterDefinition[] | [] | The filters, in order: each one a labelled control. Keys must be unique and differ from `searchKey`; a repeated key logs a warning in development and only its first use is rendered. Definitions are never changed. |
value | FilterState | — | Each filter value by key (controlled). A missing key reads as the filter default; `null` is unset. Keys that are not filters are kept and passed back. With `showApplyButton`, the applied filters. |
defaultValue | FilterState | every filter default | The starting state when uncontrolled, read once: a new object with the same content does not reset it. |
onValueChange | (value: FilterState) => void | — | Called with the whole state, every filter key present: on each change, or with `showApplyButton` on Apply. Reset and chip removal call it too. |
onApply | (value: FilterState) => void | — | Apply was pressed (or Enter in the search or a text filter) with `showApplyButton`; gets the state just applied. |
onReset | () => void | — | Reset was pressed, once per press, after the filters went back to their defaults. Refresh your data here if you need to; Filter Bar fetches nothing. |
searchable | boolean | false | Adds a search field. Its text is in the state under `searchKey`. |
searchKey | string | 'search' | The key of the search text in the state: the one place it is kept. `null` when empty. |
searchPlaceholder | string | 'Search' | Placeholder of the search field. |
layout | 'horizontal' | 'vertical' | 'responsive' | 'horizontal' | `horizontal`: a row of controls that wraps. `vertical`: a column of full-width controls, for a side panel. `responsive`: a column on phones and a row from the `md` breakpoint. |
size | 'sm' | 'md' | 'lg' | 'md' | Gap, padding, control widths, and the size of every control and button. |
variant | 'default' | 'outline' | 'filled' | 'ghost' | 'default' | The container only: `default` the page background, `outline` adds a border, `filled` a muted surface, `ghost` no fill, border or padding (above a table or inside a card). |
wrap | boolean | true | In a row, controls that do not fit move onto new lines. Turn off only when the bar always has room. |
showLabels | boolean | true | Visible labels above the controls. When false the labels still name the controls, and the bar is one line high. |
showApplyButton | boolean | false | Changes stay a draft until Apply: `value` and `onValueChange` are then the applied state. Apply is disabled while nothing changed or a range is invalid. |
showResetButton | boolean | false | A Reset button that puts every filter and the search back to their defaults (the "clear all" action). Disabled when there is nothing to reset. |
showActiveCount | boolean | false | A badge with the number of active filters ("2 filters applied"), hidden at zero. |
showActiveFilters | boolean | false | Active filters as chips ("Status: Active"); each chip removes its filter (back to its default). |
collapsible | boolean | false | Puts the filters marked `advanced` behind a "More filters" button that counts the active ones. With none marked, every filter goes behind it. |
disabled | boolean | false | Disables every control, chip and action. |
leading | ReactNode | — | Content at the start, such as a title. A string is shown as Text. |
trailing | ReactNode | — | Content at the end, after the actions, such as an Export button. |
locale | string | the device's | Locale of the date pickers and of the dates in chips. |
labels | Partial<FilterBarLabels> | — | Every text Filter Bar writes (Apply, Reset, "More filters", the count, remove and clear button names, "All", "Yes", "No"…), for translation. |
aria-label | string | 'Filters' | Names the bar, a `group`. Name it when a page has several. |
gap | SpaceTokens | number | from `size` | Space between controls and between rows. |
padding | SpaceTokens | number | from `size`, none for `ghost` | `paddingHorizontal`, `paddingVertical` and the shorthands work too. |
backgroundColor | ColorTokens | from `variant` | Overrides the variant surface. |
borderColor | ColorTokens | 'border' (outline) | Overrides the outline color. Other variants show it with a `borderWidth`. |
borderRadius | RadiusTokens | number | 'lg' | Corner radius of the container. |
FilterDefinition
One filter. `type` picks the control: `select` (Select), `multi-select` (Multi Select), `boolean` (a Select of All, Yes and No), `date` (Date Picker), `date-range` (Date Range Picker), `text` (Input) or `custom`.| Prop | Type | Default | Description |
|---|---|---|---|
key required | string | — | Unique and stable: the filter key in the state. |
label required | string | — | Names the control and starts its chip. |
type required | 'select' | 'multi-select' | 'boolean' | 'date' | 'date-range' | 'text' | 'custom' | — | The control, and the shape of the value (see Values). |
options | FilterOption[] | — | `select` and `multi-select`: `{ label, value, disabled? }`. Values may be strings or numbers (and booleans in a select); they go into the state as given, never their labels. |
defaultValue | FilterValue | null | What a missing key reads as, what Reset and a chip restore, and a value that is not counted as active. |
placeholder | string | — | Shown while unset. For `select` and `boolean` also the first option, which unsets the filter. Default "All" for those and `multi-select`. |
trueLabel | string | 'Yes' | `boolean`: the option and chip text for `true`. |
falseLabel | string | 'No' | `boolean`: the option and chip text for `false`. |
advanced | boolean | false | With `collapsible`, the filter sits behind "More filters". |
disabled | boolean | false | Disables this filter. |
render | (context: CustomFilterContext) => ReactNode | — | `custom`: the control. The context has `value`, `onValueChange`, `reset`, `size`, `disabled` and, while labels are hidden, `aria-label`. Core form controls inside take the label from the filter Field. |
format | (value) => string | — | `custom`: how the value reads in its chip. Default: the value as text. |
Values
Unset is always null: an emptied text field, an empty multi-select, and a range with neither end are reported as null. false is a value, not unset.
| Type | Value | Counted and shown as a chip when |
|---|---|---|
select | the option value (string, number or boolean) | set and not its default |
multi-select | an array of option values, no repeats | one or more picked; counts once |
boolean | true or false | either one, false included, unless it is the default |
date | a Date at local midnight, as Calendar gives it; never converted to UTC | set |
date-range | { start: Date | null, end: Date | null }; one end may be open | either end set; counts once |
text | the text as typed (no trimming or case changes) | not empty |
custom | any FilterValue | set and not its default |
| search | the text under searchKey | never: it has its own field and clear button |
Apply, Reset and chips
Filter Bar never fetches or queries: it reports the state, and your app filters its data.
| Action | Without Apply button | With `showApplyButton` |
|---|---|---|
| Change a control | onValueChange at once | Changes the draft only |
| Apply | — | onValueChange and onApply with the draft |
| Reset | Defaults for every filter and the search; onValueChange, then onReset | The same, applied at once; the draft is reset too |
| Remove a chip | That filter back to its default; onValueChange | The same, applied at once; other pending edits stay |
Parent sets value | Shown | Shown, and replaces the draft (when the content differs) |
Changing definitions
| Change | What happens |
|---|---|
| A filter is added | Its key reads as its default until it is set. |
| A filter is removed | Its value stays in the state like any other key, and is no longer shown, counted or reset. |
| Options change | The value is kept. A value no longer among the options shows the placeholder in the control and its raw value in the chip. |
| A default changes | Reset and the count use the new default; the current state is not changed. |
Accessibility
- The bar is a
groupnamed byaria-label("Filters" by default). Every control keeps its own Tab stop and its own keyboard behavior; Filter Bar adds no arrow-key handling. - Each filter is a Field: its visible label is linked to the control, or, with
showLabels={false}, names it witharia-label. Select, Multi Select, Date Picker and Date Range Picker keep their own roles, states and keyboard support; disabled options are announced as disabled. - The search field is a named
searchboxwith its own clear button. Text and date filters get a clear button ("Clear Created") once they have a value; clearing moves focus back to the control. - Chips sit in a
groupnamed "Active filters". Each remove button is named from the chip ("Remove Status: Active"). On web, removing one moves focus to the chip that took its place, or to the bar when none is left. - The count is text ("2 filters applied"), not a color or a dot. The "More filters" button has
aria-expandedandaria-controls, and its name includes the count of active filters behind it. - A date range that ends before it starts is marked invalid (
aria-invalid) with an error linked to the field; it is never swapped. Apply, Reset and Clear are disabled, not hidden, when they would do nothing. - Nothing is a live region: the controls announce their own changes, so the bar adds no second announcement.
| Key | Action |
|---|---|
| Tab | Moves through the search field, each filter (and its clear button), the chips, and the actions. |
| Enter | In the search field or a text filter with `showApplyButton`: applies. On a chip remove button: removes the filter. |
| Escape | In the search field: clears it (web). Closes an open list or calendar. |
Responsive behavior
In a row, each control has a width from size (wider for multi-select and date-range filters), never wider than the bar, and the search field takes the free space. With wrap (on by default) controls that do not fit move onto new lines, and the actions stay at the end edge. layout="vertical" stacks full-width controls for a side panel or sheet, and layout="responsive" does so below the md breakpoint (768px) and switches to a row above it. Chips wrap. Nothing has a fixed height, so long labels and larger text grow the bar instead of being clipped. For many filters on phones, mark the less used ones advanced and set collapsible.
Platform notes
div with role="group". Selects open a listbox, and the date pickers a popover. Removing a chip moves focus (DOM focus).