# FilterBar

> A responsive row of search, filters, results count and "Clear all".

Category: Advanced · Status: stable · Package: @bindshq/stance-react@0.1.1 · Docs: https://stanceui.com/components/filter-bar

FilterBar assembles the filtering controls above a table or list: an optional search field, any number of FilterChips, a "Clear all" action and a results count that screen readers hear when it changes. It wraps on narrow screens without breaking the page.

## Import

```tsx
import { FilterBar, FilterChip } from '@bindshq/stance-react';
```

Styles are imported once per app: `@bindshq/stance-tokens/css` and `@bindshq/stance-react/styles.css`.

## Examples

### Basic

```tsx
import * as React from 'react';
import { Button, FilterBar, FilterChip } from '@bindshq/stance-react';

const users = [
  { name: 'Amara Okafor', department: 'Sales' },
  { name: 'Lukas Novak', department: 'Engineering' },
  { name: 'Priya Raman', department: 'Legal' },
  { name: 'Hana Sato', department: 'Sales' },
  { name: 'Oliver Grant', department: 'Finance' },
];

export function Basic() {
  const [query, setQuery] = React.useState('');
  const [department, setDepartment] = React.useState<string | null>(null);
  const results = users.filter(
    (u) =>
      u.name.toLowerCase().includes(query.toLowerCase()) &&
      (!department || u.department === department),
  );
  return (
    <div style={{ display: 'grid', gap: 16 }}>
      <FilterBar
        search={{
          value: query,
          onChange: setQuery,
          label: 'Search users',
          placeholder: 'Search by name',
        }}
        resultCount={results.length}
        onClearAll={() => {
          setQuery('');
          setDepartment(null);
        }}
        canClear={Boolean(query || department)}
      >
        <FilterChip
          label="Department"
          value={department ?? undefined}
          onClear={() => setDepartment(null)}
        >
          <div role="group" aria-label="Departments" style={{ display: 'grid', gap: 4 }}>
            {['Sales', 'Engineering', 'Legal', 'Finance'].map((d) => (
              <Button
                key={d}
                size="sm"
                variant={department === d ? 'primary' : 'ghost'}
                onClick={() => setDepartment(d)}
              >
                {d}
              </Button>
            ))}
          </div>
        </FilterChip>
      </FilterBar>
      <ul style={{ margin: 0, paddingLeft: 20 }}>
        {results.map((u) => (
          <li key={u.name}>
            {u.name} · {u.department}
          </li>
        ))}
      </ul>
    </div>
  );
}
```

### With trailing actions

```tsx
import * as React from 'react';
import { Button, FilterBar, FilterChip, Icon } from '@bindshq/stance-react';

export function WithActions() {
  const [q, setQ] = React.useState('');
  return (
    <FilterBar
      search={{ value: q, onChange: setQ, label: 'Search templates' }}
      resultCount={24}
      formatResultCount={(n) => `${n} templates`}
      actions={
        <Button size="sm" variant="outline" leadingIcon={<Icon name="arrows-up-down" />}>
          Sort
        </Button>
      }
    >
      <FilterChip label="Owner" />
      <FilterChip label="Updated" value="This month" onClear={() => {}} />
    </FilterBar>
  );
}
```

### Search only

```tsx
import * as React from 'react';
import { FilterBar } from '@bindshq/stance-react';

export function SearchOnly() {
  const [q, setQ] = React.useState('');
  return (
    <FilterBar
      search={{
        value: q,
        onChange: setQ,
        label: 'Search contracts',
        placeholder: 'Contract or counterparty',
      }}
    />
  );
}
```

## API reference

| Component | Prop | Type | Default | Description |
| --- | --- | --- | --- | --- |
| FilterBar | `children` | `ReactNode` | None | FilterChips and other filter controls. |
| FilterBar | `search` | `{ value: string; onChange: (v: string) => void; label?: string; placeholder?: string }` | None | Optional search field. |
| FilterBar | `onClearAll` | `() => void` | None | Shows "Clear all" when provided. |
| FilterBar | `canClear` | `boolean` | `true` | Whether any filter is active. |
| FilterBar | `resultCount` | `number` | None | Results count, announced politely on change. |
| FilterBar | `formatResultCount` | `(count: number) => string` | `"N results"` | Formats the count. |
| FilterBar | `actions` | `ReactNode` | None | Trailing content such as sort or view controls. |
| FilterBar | `label` | `string` | `'Filters'` | Accessible name of the group. |

## Accessibility

- The bar is a labelled group ("Filters").
- The results count is a polite live region (role="status"), so screen readers announce new totals without moving focus.
- The search field is a native search input with an accessible label.
- Hide "Clear all" with canClear={false} when nothing is active, so it is not a dead control.

## Keyboard

| Keys | Action |
| --- | --- |
| `Tab` | Moves through search, each filter, its clear button and Clear all. |

## Guidelines

**Do**

- Keep the most used filters visible; move rare ones into a "More filters" popover.

**Don’t**

- Announce counts on every keystroke for slow searches; debounce first.

## Related

- https://stanceui.com/components/filter-chip
- https://stanceui.com/components/data-table
- https://stanceui.com/components/search-input
