Structured filters as chips above any collection — tables, boards, calendars, galleries. “+ Filter” walks field → condition + value in one popover; every chip segment edits in place. Operators follow the field type, dates can be relative, options can load from the server, and the value is a plain FilterGroup you can put in a URL or a saved view.
88 of 400 deals match.
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { FilterBar, applyFilters, type FilterFieldDef, type FilterGroup } from "@vamos-ui/react/data-table";
const fields: FilterFieldDef[] = [
{ field: "stage", label: "Stage", type: "enum", options: stageOptions, quick: true },
{ field: "value", label: "Deal value", type: "currency" },
];
const [filters, setFilters] = React.useState<FilterGroup>({ logic: "and", clauses: [] });
<FilterBar fields={fields} value={filters} onChange={setFilters} resultCount={applyFilters(deals, filters, fields).length} />Keyboard: the bar is one toolbar tab stop; ←/→, Home/End move between chip segments. In the popover type to search fields, Backspace on an empty input goes back a step, Enter applies, Esc cancels.
Matching is locale-aware (Turkish İ/ı folding) and between is inclusive; dates compare by calendar day in the provider’s time zone. A server must implement the same semantics (see filter-fns.ts).
Examples
Quick filters with counts (HR)
quick: true pins an empty dashed chip; facets show how many rows each option would keep.
120 people
Server-searched options
options can be (q) => Promise<FilterOption[]> — debounced, with a spinner, labels cached for the chips.
Relative dates
Presets and “Last N days/weeks/months”, resolved at query time so saved views stay current.
Resolves to Aug 13 – Nov 10, 2026 (today: Nov 10, 2026).
Above a board, synced to the URL
Not table-bound: encodeFilters / decodeFilters keep a standalone bar in ?board_f=…
Lead 25
Qualified 18
Proposal 6
Won 6
Read-only, sheet, collapsed, disabled
A shared-link preview without ×, the mobile sheet layout, maxVisibleChips and size sm.
Read-only (a shared link preview)
Sheet layout (automatic under 480px of width)
Collapsed chips · small · disabled
API
FilterBar
| Prop | Type | Default |
|---|---|---|
fieldsThe filterable fields. From a Data Table: filterFieldsFromColumns(columns). | FilterFieldDef[] | — |
value / defaultValue / onChange{ logic: "and" | "or", clauses } — controlled or uncontrolled. Incomplete clauses are never committed. | FilterGroup | — |
q / onQChange / searchPlaceholderShows a search box at the start when onQChange is set. | string · (q) => void | — |
facetsOption counts in the value list. | Record<field, Record<value, number>> | — |
resultCountAnnounced after each change (“Filter added: Value at least $5,000. 84 results.”). | number | — |
maxVisibleChipsCollapse the rest into “+N”. | number | — |
layoutauto switches to a “Filters (n)” button + bottom Sheet under 480px of container width. | "auto" | "inline" | "sheet" | "auto" |
sizeChip height (md follows --control-h). | "sm" | "md" | "md" |
disabled / readOnlyreadOnly hides × and the add button (shared-link previews). | boolean | — |
meAdds “Me” to user fields, stored as the symbolic "$me". | { id, name } | — |
addRequestOpen the editor for a field programmatically (Data Table “Filter by this column”). | { field, key } | null | — |
actionsExtra controls at the end of the bar. | ReactNode | — |
labelsAll strings, including operator phrases (op_in…) and the {field} {op} {value} sentence template for word order. | Partial<FilterBarLabels> | — |
FilterFieldDef
| Prop | Type | Default |
|---|---|---|
field / label / typetext, number, currency, percent, date, datetime, enum, boolean, user, tags. | string · string · FieldType | — |
opsAllowed operators. Defaults per type: text contains…, numbers eq/gt/between…, dates on/before/after/between/relative, enum/user/tags in/notIn/isEmpty. | FilterOp[] | — |
optionsStatic or server-searched options ({ value, label, icon?, tone?, keywords? }). | FilterOption[] | (q) => Promise<FilterOption[]> | — |
quickAlways visible as an empty dashed chip. | boolean | — |
multipleEnum: false = single value (eq / neq). | boolean | true |
group / icon / placeholder / currencyPicker section, icon, value placeholder, ISO currency. | string · Component · string · string | — |
accessorapplyFilters: read the value from a row (default row[field]). | (row) => unknown | — |
FilterChip
| Prop | Type | Default |
|---|---|---|
clause / fieldField │ condition │ value │ × — each segment is a button with a full name. | FilterClause · FilterFieldDef | — |
onEdit / onRemoveDelete / Backspace on × also removes. | (step: "field" | "value", trigger) => void · () => void | — |
readOnly / disabled / sizePresentation states. | boolean · boolean · sm | md | — |