Marble UI
← Components

Filter Bar / Filter Menu

Preview: Default

"Add filter" menu (field → options) plus removable chips for active filters, like Linear/Notion filter rows.

Import

import { FilterBar } from '@marbleui/ui'

Props

<FilterBar>

PropTypeDefaultDescription
fields *requiredFilterField[]—
onChange *required(value: FilterValue) => void—
value *requiredFilterValue—
classNamestring—

Accessibility

Keyboard interaction, focus management and ARIA semantics come from the underlying primitive: React Aria Button. Follow the primitive's docs for the full keyboard map.

Usage notes

Row of filter chips with an “Add filter” menu: pick a field, then one or more of its options. For free-text search use SearchField; it pairs well with DataGrid’s toolbar.

const [filters, setFilters] = useState<FilterValue>({ status: ['todo'] })
<FilterBar fields={fields} value={filters} onChange={setFilters} />
const visible = applyFilters(issues, filters, (issue, field) => String(issue[field as keyof Issue]))
  • Controlled: value maps field ids to selected option ids, e.g. { status: ['todo', 'doing'] }. Removing a chip deletes its key; Clear sets {}.
  • applyFilters keeps rows where every active field equals one of its selected options (AND across fields, OR within a field). For server data, send the FilterValue to your API instead.
  • Only “is” / “is any of” over fixed options: no text, date, range or “is not”.
  • Give an option a textValue when its label isn’t a plain string; it’s used for typeahead and the chip text. Each chip’s button is named by field, operator and value (“Status is any of Todo, Doing”).
Example source · show
import { useState } from 'react'
import { Badge } from '../badge'
import { applyFilters, FilterBar, type FilterField, type FilterValue } from './filter-bar'

const I = ({ d }: { d: string }) => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden><path d={d} /></svg>
)
const fields: FilterField[] = [
  { id: 'status', label: 'Status', icon: <I d="M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20" />, options: [{ id: 'todo', label: 'Todo' }, { id: 'doing', label: 'In progress' }, { id: 'done', label: 'Done' }] },
  { id: 'priority', label: 'Priority', icon: <I d="M3 3v18h18M7 14l4-4 4 4 5-5" />, options: [{ id: 'high', label: 'High' }, { id: 'medium', label: 'Medium' }, { id: 'low', label: 'Low' }] },
  { id: 'label', label: 'Label', icon: <I d="M12 2H2v10l9.3 9.3a1 1 0 0 0 1.4 0l8.6-8.6a1 1 0 0 0 0-1.4z" />, options: [{ id: 'bug', label: 'Bug' }, { id: 'feature', label: 'Feature' }, { id: 'design', label: 'Design' }] },
]
const issues = [
  { id: 'ACME-140', title: 'Login redirect', status: 'doing', priority: 'high', label: 'bug' },
  { id: 'ACME-141', title: 'Pricing table', status: 'todo', priority: 'medium', label: 'design' },
  { id: 'ACME-142', title: 'Data grid resizing', status: 'doing', priority: 'medium', label: 'feature' },
  { id: 'ACME-143', title: 'Dark mode toggle', status: 'done', priority: 'low', label: 'feature' },
  { id: 'ACME-144', title: 'Crash on paste', status: 'todo', priority: 'high', label: 'bug' },
]

function Demo() {
  const [value, setValue] = useState<FilterValue>({ status: ['todo', 'doing'] })
  const rows = applyFilters(issues, value, (r, f) => r[f as 'status'])
  return (
    <div className="flex w-[34rem] flex-col gap-3">
      <FilterBar fields={fields} value={value} onChange={setValue} />
      <ul className="divide-y divide-border rounded-lg border border-border bg-surface text-sm">
        {rows.map((r) => (
          <li key={r.id} className="flex items-center gap-3 px-3 py-2">
            <span className="w-20 text-fg-subtle tabular-nums">{r.id}</span>
            <span className="flex-1 text-fg">{r.title}</span>
            <Badge>{r.label}</Badge>
          </li>
        ))}
        {!rows.length && <li className="px-3 py-6 text-center text-fg-muted">No issues match.</li>}
      </ul>
    </div>
  )
}

export const styles = { Default: Demo }

Source: packages/ui/src/filter-bar/filter-bar.tsx

Catalog

Category
data-grid
Priority
Pro
Primitive
composed
React Aria
Menu + TagGroup