Filter sheet
The mobile idiom for filtering a list. The inline filter row of the desktop moves into a bottom sheet behind a single trigger that shows how many filters are active. It composes the same ChipGroup / FilterChip as the desktop filter bar.
<FilterSheet value={filters} onValueChange={setFilters} groups={[ { id: 'status', label: 'Status', options: [ { value: 'on-track', label: 'On track', count: 2 }, { value: 'overdue', label: 'Overdue', count: 1 }, ] }, { id: 'team', label: 'Team', options: [ { value: 'platform', label: 'Platform' }, { value: 'ops', label: 'Ops' }, ] }, ]}/>Live
Tap the filter action in the header. The sheet edits a draft; only Apply commits it, and the trigger shows the active count.
Staged editing
Opening seeds a draft from the committed value; the chips mutate the draft; Apply commits it through onValueChange and closes; Cancel or dismiss discards it; Clear empties it. This is why the trigger count can stay stable while the user edits. Option values must be unique across groups, since the committed value is one flat array.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
groups | FilterGroupDef[] | — | { id, label, options, multiple? } per section. |
value | string[] | — | Committed selection across all groups. |
onValueChange | (next: string[]) => void | — | Called on Apply. |
triggerLabel | string | 'Filter' | Label / aria-label on the trigger. |
iconOnly | boolean | false | Borderless icon-only trigger with an active dot; suits a header action. |
title | string | 'Filters' | Sheet title. |