The example below loads live from Storybook; open it in Storybook for the controls.
Default
Section titled “Default”FilterBar keeps the filters as one typed object. You define the controls with fields and manage the value with value and onValueChange. In the default instant mode every change applies at once. Applied filters show as chips, each with its own remove button. “Clear filters” appears only when a filter is applied.
import { useState } from 'react';import { FilterBar, type FilterField } from '@bigbang/ui';
type Filters = { query: string; status: string | null; category: string[]; mine: boolean };
const empty: Filters = { query: '', status: null, category: [], mine: false };
const fields: ReadonlyArray<FilterField<Filters>> = [ { key: 'query', label: 'Search', kind: 'search', placeholder: 'Search by name' }, { key: 'status', label: 'Status', kind: 'select', options: [ { value: 'active', label: 'Active' }, { value: 'draft', label: 'Draft' }, { value: 'paused', label: 'Paused' }, ], }, { key: 'category', label: 'Category', kind: 'multi-select', options: [ { value: 'a', label: 'Category A' }, { value: 'b', label: 'Category B' }, { value: 'c', label: 'Category C' }, ], }, { key: 'mine', label: 'Mine only', kind: 'toggle' },];
export default function Default() { const [value, setValue] = useState<Filters>({ ...empty, status: 'active', category: ['a'] }); return ( <div className="w-full"> <FilterBar fields={fields} value={value} onValueChange={setValue} resultCount={12} /> </div> );}Batch apply
Section titled “Batch apply”With apply="batch" changes collect in a draft. The Apply button is enabled only when the draft differs from the applied value; Reset undoes the draft. Enter in the search box applies too. Chips always show the applied value.
import { useState } from 'react';import { FilterBar, type FilterField } from '@bigbang/ui';
type Filters = { query: string; status: string | null; category: string[]; mine: boolean };
const empty: Filters = { query: '', status: null, category: [], mine: false };
const fields: ReadonlyArray<FilterField<Filters>> = [ { key: 'query', label: 'Search', kind: 'search', placeholder: 'Search by name' }, { key: 'status', label: 'Status', kind: 'select', options: [ { value: 'active', label: 'Active' }, { value: 'draft', label: 'Draft' }, { value: 'paused', label: 'Paused' }, ], }, { key: 'category', label: 'Category', kind: 'multi-select', options: [ { value: 'a', label: 'Category A' }, { value: 'b', label: 'Category B' }, { value: 'c', label: 'Category C' }, ], }, { key: 'mine', label: 'Mine only', kind: 'toggle' },];
export default function Batch() { const [value, setValue] = useState<Filters>(empty); return ( <div className="w-full"> <FilterBar fields={fields} value={value} onValueChange={setValue} apply="batch" resultCount={12} /> </div> );}With a table
Section titled “With a table”FilterBar does not filter anything; it only produces the value. You filter the rows and give the number back with resultCount. The count is announced to screen readers once, 500 ms after the filters change.
| Name | Status | Owner |
|---|---|---|
| First item | Active | User 1 |
| Second item | Draft | User 2 |
| Third item | Paused | User 1 |
| Fourth item | Active | User 2 |
| Fifth item | Draft | User 1 |
| Sixth item | Active | User 2 |
import { useState } from 'react';import { FilterBar, Table, type FilterField } from '@bigbang/ui';
type Filters = { query: string; status: string | null; category: string[]; mine: boolean };
const empty: Filters = { query: '', status: null, category: [], mine: false };
const fields: ReadonlyArray<FilterField<Filters>> = [ { key: 'query', label: 'Search', kind: 'search', placeholder: 'Search by name' }, { key: 'status', label: 'Status', kind: 'select', options: [ { value: 'active', label: 'Active' }, { value: 'draft', label: 'Draft' }, { value: 'paused', label: 'Paused' }, ], }, { key: 'category', label: 'Category', kind: 'multi-select', options: [ { value: 'a', label: 'Category A' }, { value: 'b', label: 'Category B' }, { value: 'c', label: 'Category C' }, ], }, { key: 'mine', label: 'Mine only', kind: 'toggle' },];
const rows = [ { name: 'First item', status: 'active', category: 'a', mine: true }, { name: 'Second item', status: 'draft', category: 'b', mine: false }, { name: 'Third item', status: 'paused', category: 'c', mine: true }, { name: 'Fourth item', status: 'active', category: 'a', mine: false }, { name: 'Fifth item', status: 'draft', category: 'b', mine: true }, { name: 'Sixth item', status: 'active', category: 'c', mine: false },];
export default function WithTable() { const [value, setValue] = useState<Filters>(empty); const shown = rows.filter( (row) => row.name.toLocaleLowerCase().includes(value.query.trim().toLocaleLowerCase()) && (value.status === null || row.status === value.status) && (value.category.length === 0 || value.category.includes(row.category)) && (!value.mine || row.mine), ); return ( <div className="grid w-full gap-xl"> <FilterBar fields={fields} value={value} onValueChange={setValue} resultCount={shown.length} /> <Table caption="Item list" captionHidden> <Table.Header> <Table.Row> <Table.HeaderCell>Name</Table.HeaderCell> <Table.HeaderCell>Status</Table.HeaderCell> <Table.HeaderCell>Owner</Table.HeaderCell> </Table.Row> </Table.Header> <Table.Body> {shown.map((row) => ( <Table.Row key={row.name}> <Table.RowHeader>{row.name}</Table.RowHeader> <Table.Cell>{fields[1]?.options?.find((option) => option.value === row.status)?.label}</Table.Cell> <Table.Cell>{row.mine ? 'User 1' : 'User 2'}</Table.Cell> </Table.Row> ))} {shown.length === 0 ? ( <Table.Row> <Table.Cell colSpan={3}>No item matches the filters</Table.Cell> </Table.Row> ) : null} </Table.Body> </Table> </div> );}Narrow screens
Section titled “Narrow screens”Below 1024 px the search box stays in place; the other filters move into an inline panel opened by the “More filters” button. The button shows the number of applied filters in a badge. A drawer for very narrow screens is not built yet.
Light and dark mode
Section titled “Light and dark mode”FilterBar takes every colour from tokens, and the controls inside follow their own modes.
import { useState } from 'react';import { FilterBar, type FilterField } from '@bigbang/ui';
type Filters = { query: string; status: string | null; category: string[]; mine: boolean };
const empty: Filters = { query: '', status: null, category: [], mine: false };
const fields: ReadonlyArray<FilterField<Filters>> = [ { key: 'query', label: 'Search', kind: 'search', placeholder: 'Search by name' }, { key: 'status', label: 'Status', kind: 'select', options: [ { value: 'active', label: 'Active' }, { value: 'draft', label: 'Draft' }, { value: 'paused', label: 'Paused' }, ], }, { key: 'category', label: 'Category', kind: 'multi-select', options: [ { value: 'a', label: 'Category A' }, { value: 'b', label: 'Category B' }, { value: 'c', label: 'Category C' }, ], }, { key: 'mine', label: 'Mine only', kind: 'toggle' },];
export default function Default() { const [value, setValue] = useState<Filters>({ ...empty, status: 'active', category: ['a'] }); return ( <div className="w-full"> <FilterBar fields={fields} value={value} onValueChange={setValue} resultCount={12} /> </div> );}