Organisms

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.

Updated Aug 1, 2026 by Leonardo Posada

Live preview

Status: 2Provider: StripeCardLast 7 days
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.

Status: 2Provider: StripeCardLast 7 days
  1. 1
    Add filter

    Outline sm button with a FunnelSimple icon. Opens the master-detail picker Popover.

  2. 2
    Master-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.

  3. 3
    Applied tags

    One removable secondary chip per active filter, labelled 'Filter: value' (or 'Filter: N' for multiple). The X clears that filter.

  4. 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.

  5. 5
    Clear 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.

Empty

No filters applied yet — just Add filter, the separator, and a muted hint.

No filters applied
<FilterBar filters={filters} />
Filters applied (+N more, Clear filters)

Five filters applied: four tags show, the fifth collapses into +1 more, and Clear filters appears at 2+.

Status: 2Provider: StripeCardLast 7 days
<FilterBar
  filters={filters}
  defaultValue={{
    status: ["succeeded", "pending"],
    provider: ["stripe"],
    method: ["card"],
    date: ["7d"],
    amount: ["50-200"],
  }}
/>
Required (fixed-context) tags

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.

CardLast 7 days
<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"] }}
/>
Master-detail picker

Add filter opens the picker. The checkbox and provider panels carry Select all / Inverse / None; every panel has a search over its options.

No filters applied
Date range

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.

No filters applied

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.

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.

PropTypeDefaultDescription
filtersFilterDef[]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.
valueFilterValueControlled applied selections, keyed by filter id (Record<string, string[]>). Radio and date-range hold a single value.
defaultValueFilterValueUncontrolled initial selections.
onChange(value: FilterValue) => voidFires on any selection change.
maxVisibleTagsnumber4Tags shown before the rest collapse into '+N more'.
classNamestringMerged 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

Do
  • 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
  • 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.