Filter bar
The applied-filters bar that sits above a list or table. An Add filter button opens a master-detail picker: filter categories on the left, the selected category's control on the right (a checkbox or radio grid, a provider grid, or a date-range preset list). Each active filter renders a removable tag. Selections apply live. On desktop up to 4 tags show, the rest collapse into a +N more chip, and a Clear filters link appears once 2 or more filters are applied.
Live preview
import { FilterBar, type FilterDef } from "@/components/organisms/filter-bar";
const filters: FilterDef[] = [
{ id: "status", label: "Status", icon: CheckCircle, type: "checkbox",
options: [{ value: "succeeded", label: "Succeeded" }, /* … */] },
{ id: "provider", label: "Provider", icon: CreditCard, type: "provider",
options: [{ value: "stripe", label: "Stripe", logo: stripeLogo }, /* … */] },
{ id: "method", label: "Payment method", type: "radio",
options: [{ value: "card", label: "Card" }, /* … */] },
{ id: "date", label: "Date range", type: "date-range",
options: [{ value: "custom", label: "Custom" },
{ value: "7d", label: "Last 7 days", hint: "Dec 29 - Jan 3" }, /* … */] },
];
<FilterBar filters={filters} onChange={setApplied} />Anatomy
A single row: the Add filter button, a vertical Separator, then either the No filters applied hint or the applied tags. The picker is a Popover whose content is the master-detail menu. Everything is config-driven from a filters array; each entry declares its type (checkbox, radio, provider, date-range) and options.
- 1Add filter
Outline sm button with a FunnelSimple icon. Opens the master-detail picker Popover.
- 2Master-detail picker
A bordered rail of filter categories on the left (icon + label, active row highlighted with a caret) and a 448px content panel on the right: an optional search, the title with Select all (Inverse / None appear once something is selected), and the type-specific control.
- 3Applied tags
One removable secondary chip per active filter, labelled 'Filter: value' (or 'Filter: N' for multiple). The X clears that filter.
- 4+N more
A primary chip shown when more than maxVisibleTags (default 4) filters are applied. Hovering it lists the hidden filters in a dark tooltip.
- 5Clear filters
A link button that clears everything. Appears only when 2 or more filters are applied.
States & content types
The bar is empty, or carries tags. The picker's right panel changes with the active filter's type: a multi-select checkbox grid (with Select all / Inverse / None), a single-select radio grid, a provider grid with logos, or a date-range preset list that expands into date + time pickers under Custom.
No filters applied yet — just Add filter, the separator, and a muted hint.
<FilterBar filters={filters} />Five filters applied: four tags show, the fifth collapses into +1 more, and Clear filters appears at 2+.
<FilterBar
filters={filters}
defaultValue={{
status: ["succeeded", "pending"],
provider: ["stripe"],
method: ["card"],
date: ["7d"],
amount: ["50-200"],
}}
/>Filters marked `required: true` land as tags without the X remove button — they're fixed context the surface always needs (time range, currency, granularity). Clear filters skips them (and the ≥2 threshold that shows Clear filters counts only removable tags). Users can still change the value from the popover.
<FilterBar
filters={[
// "date" and "method" are fixed context for this surface
{ id: "date", label: "Date range", type: "date-range", required: true, options: [/* … */] },
{ id: "method", label: "Payment method", type: "radio", required: true, options: [/* … */] },
/* other, removable filters */
]}
defaultValue={{ date: ["7d"], method: ["card"] }}
/>Add filter opens the picker. The checkbox and provider panels carry Select all / Inverse / None; every panel has a search over its options.
A preset radio list (Last 3 / 7 / 30 days, This month, All time) with a muted date hint; picking Custom reveals Start / End date and two time fields.
Import
Pass a filters array; the bar owns the applied state (or control it with value / onChange). Composes the kit Button, Checkbox, RadioGroup, DatePicker, Popover and Separator.
import { FilterBar, type FilterDef } from "@/components/organisms/filter-bar";Props
The whole bar is driven by the filters config and the applied value map.
| Prop | Type | Default | Description |
|---|---|---|---|
| filters | FilterDef[] | — | The available filter categories (the left rail). Each: { id, label, icon?, type: 'checkbox' | 'radio' | 'provider' | 'date-range', required?: boolean, options }. Mark required for fixed-context filters (time range, currency, granularity) — their tag has no X, Clear filters skips them, users can still change the value from the popover. |
| value | FilterValue | — | Controlled applied selections, keyed by filter id (Record<string, string[]>). Radio and date-range hold a single value. |
| defaultValue | FilterValue | — | Uncontrolled initial selections. |
| onChange | (value: FilterValue) => void | — | Fires on any selection change. |
| maxVisibleTags | number | 4 | Tags shown before the rest collapse into '+N more'. |
| className | string | — | Merged onto the bar row via cn(). |
When to use
- Above any list or table that needs faceted filtering: Payments, Transactions, Reconciliations, Payouts.
- When filters span different shapes — statuses (checkbox), a single method (radio), providers (logos), a date range — under one Add filter entry point.
- Whenever applied filters should stay visible and individually removable as tags.
When not to use
- For a single always-visible dropdown (one facet) — a plain Select or Combobox is lighter.
- For free-text search — that is the Input in the search toolbar, not a filter tag.
- For navigation between views (tabs) — filters narrow one list, they don't switch screens.
Usage
- Give each filter a clear icon and label; they are the left rail and the tag prefix.
- Let selections apply live so the tags track the picker in real time.
- Keep the desktop tag limit at 4 and let the rest live behind +N more.
- Don't add an Apply button — the Figma applies live, and tags are the feedback.
- Don't colour the tags or the Clear link red — removing a filter is not destructive.
- Don't wrap the provider logos in bordered tiles — the CDN logos are self-contained squares.
Related
Cross-links to atoms and patterns you may reach for next.
- CheckboxThe multi-select control inside the checkbox and provider panels.
- Radio groupThe single-select control for radio and date-range presets.
- Date pickerStart / End date fields under Custom in the date-range panel.
- PopoverHosts the master-detail picker and the +N more overflow.