# FilterBar

> The one filter row above every table: a growing search, dropdown chips at the end, clear, and the list's own action.

Category: Navigation. Docs page: https://rime.mjsons.net/components/filter-bar

## Installation

```bash
npx shadcn@latest add KezLahd/rime/filter-bar
```

FilterBar lays out a list's filters in a fixed order: a FilterSearch that grows to fill the row, then Select chips (and a DateRangeFilter), then a ghost "Clear filters" while anything is applied, then the list's own action after a hairline ("New invoice").

It is presentational. Filter state lives with the screen, which also decides when to pass onClear (only while a filter is applied). No title, no "n of m shown" line and never a row of toggle chips.

## Usage

```tsx
import { FilterBar, FilterSearch, Select } from "@/components/ui";

<FilterBar aria-label="Filter invoices" onClear={filtering ? clear : undefined} actions={<Button size="sm" iconStart={<IconPlus size={14} />}>New invoice</Button>}>
  <FilterSearch grow aria-label="Search invoices" value={query} onChange={setQuery} placeholder="Customer or invoice number" />
  <Select appearance="chip" aria-label="Status" value={status} onChange={setStatus} active={status !== "all"} options={STATUSES} />
</FilterBar>
```

## Props: FilterBar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  | The filters, in order: a <FilterSearch grow /> first, then <Select appearance="chip" /> dropdowns. The search fills whatever the dropdowns leave, so the row always spans the table's full width. |
| `aria-label` (required) | `string` |  | Names the filter group for screen readers: "Filter seats". |
| `onClear` | `() => void` |  | Shown as a ghost "Clear filters" after the dropdowns. Pass it only while a filter is applied. |
| `clearLabel` | `string` | `"Clear filters"` |  |
| `actions` | `ReactNode` |  | The list's own action, at the far end after a hairline: "Add team member". |
| `className` | `string` |  |  |

## Props: FilterSearch

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `string` |  |  |
| `onChange` (required) | `(value: string) => void` |  |  |
| `placeholder` | `string` | `"Search"` |  |
| `aria-label` (required) | `string` |  |  |
| `className` | `string` |  |  |
| `width` | `number \| string` | `220` | Width of the chip. Defaults to 220px. Ignored when grow is set. |
| `grow` | `boolean` |  | Fill the row: the search takes every pixel the other filters leave (FilterBar's pattern, where dropdowns sit at the end). |

## Props: FilterChip

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` | `false` | Filter applied. Shown by tint and ink only, never a ring. |
| `icon` | `ReactNode` |  |  |
| `count` | `number` |  |  |
| `children` (required) | `ReactNode` |  |  |

Also accepts the native attributes of: AriaAttributes, ButtonHTMLAttributes, DOMAttributes, HTMLAttributes.

## Examples

### Search, dropdowns, clear and action

Type or pick a status to see Clear filters appear.

```tsx
<FilterBar aria-label="Filter invoices" onClear={filtering ? clear : undefined} actions={<Button size="sm">New invoice</Button>}>
  <FilterSearch grow aria-label="Search" value={q} onChange={setQ} />
  <Select appearance="chip" aria-label="Status" … />
  <DateRangeFilter aria-label="Issued" … />
</FilterBar>
```

## Theme tokens

Per-component tokens. Undeclared by default: the default below is the fallback the CSS uses. Set any of them at :root or on a wrapper to restyle every instance.

| Token | Default |
| --- | --- |
| `--chip-height` | `var(--control-h-sm)` |
| `--chip-radius` | `var(--r-sm)` |
| `--filter-bar-gap` | `14px` |

System tokens it reads: `--brand-deep`, `--chip-active-fill`, `--chip-active-fill-hover`, `--chip-active-shadow`, `--control-h-sm`, `--density`, `--dur`, `--ease`, `--focus-color`, `--focus-ring`, `--font-body`, `--ink-body`, `--ink-brand`, `--ink-disabled`, `--ink-muted`, `--line-glass`, `--r-2xs`, `--r-full`, `--r-sm`, `--recess-fill`, `--recess-fill-hover`, `--rgb-brand`, `--rgb-contact`, `--rgb-white`, `--shadow-recess`, `--text-caption`, `--wash-active`.

## Accessibility

- The filters sit in role="group" named by the required aria-label ("Filter invoices").
- Every chip needs its own aria-label: chips show a value, not a label.
- Announce result counts from the table region (a polite status line), not from the bar.

## Do

- Order: search, dropdowns, date, clear, then the action.
- Pass onClear only while a filter is applied.
- Use FilterSearch here, not SearchField, so the row's heights match.

## Don't

- Never a row of toggle chips for a single-choice filter; use a Select chip.
- Don't put a heading or result count above the bar.

Related: https://rime.mjsons.net/components/table, https://rime.mjsons.net/components/select, https://rime.mjsons.net/components/date-range-filter
