Filter multi select
Filter multi select is a filter trigger that opens a popover of checkbox options and summarizes the current selection ("All" or a count) on the trigger itself.
It shares its trigger with filter menu — both are a secondary Button with a chevron — and differs in the panel: a checkbox list with an "All" row, rather than a menu. Use a filter menu when the trigger should read as a button and the panel as a menu; use a filter multi select when it should read as an applied filter.
Example
FilterMultiSelect is the batteries-included form: one pill, one checkbox list, an All/count summary.
Usage
Selection and the "All" row
The pill reads "All" for a full selection and a count for a partial one; an empty selection shows no summary at all, leaving the pill as its bare label, so an emptied filter never looks like "All" while the filtered table is empty. The "All" row matches: checked for a full selection, mixed for a partial one, unchecked for an empty one. It toggles — selecting everything, and clearing everything from a full selection.
A caller that treats "nothing selected" as "no filter" should seed value with every option, so the pill reads "All" at rest.
Several lists in one popover
FilterOptionList renders the checkbox list without its own popover, so one FilterPill (or any popover) can hold several filters. Each list's label renders as its section header.
A popover the pill can't host
FilterPill owns its popover: uncontrolled open state, a 240px panel, and focus contained to the
option list. A filter whose panel disagrees with any of that (a controlled open, an auto-width
editor, a cascade) uses FilterPillButton as the trigger of its own popover, so it still reads as
the same pill.
PopoverTrigger injects a ref, so the trigger must be FilterPillButton itself, not a wrapper
around it.
Stale values
Selected values no longer present in options are ignored, so a deleted option can't make the pill say "All" while the popover disagrees.
Guidelines
When to use
- When a table or list is filtered by one attribute with a small set of checkbox options, and the applied filter should stay visible as a pill.
- When several such filters sit in a row above the thing they filter.
When not to use
- If the dropdown triggers one-shot actions rather than holding a selection, use a menu.
- If the trigger should look like a button rather than an applied filter, use a filter menu.
- For free-text or large option sets, use a combobox — the pill's checkbox list has no search.
Content
- Use sentence case.
Accessibility
Opening the pill focuses the first option's checkbox, and arrow keys move between the row checkboxes — across every list when several share one popover. Tab reaches each checkbox and each row's "Only" button in turn and stays inside the panel; Escape closes it and returns focus to the pill.
Clicking anywhere on a row toggles that option, the same as its checkbox; clicking the option's text also moves focus to that checkbox, so the arrow keys carry on from the option just clicked. "Only" narrows the selection to that option alone, and is the keyboard and screen-reader path to doing so. The row itself is deliberately not a control: it holds a checkbox and a button, and giving it a role of its own would nest interactive elements.
Props
FilterMultiSelect
A FilterPill with a single checkbox FilterOptionList and an All/count summary. Reach for this first; drop to the parts below only to compose several lists in one popover.
| Name | Default | Description |
|---|---|---|
label | — | stringFilter name shown on the pill and above the option list. |
value | — | Set<T>Selected option values. |
onChange | — | (value: Set<T>) => voidCallback with the next selection when the user toggles options. |
options | — | FilterPillOption<T>[]Selectable options. |
variant | — | "pill"Trigger appearance. "pill" is the rounded filter pill. |
showOnlyAffordance | true | booleanReveals a "Only" affordance on each row that narrows the selection to it. |
FilterPill
The pill trigger and its popover shell. summary renders next to the label; children is the popover body.
| Name | Default | Description |
|---|---|---|
children | — | ReactNodePopover contents, typically one or more FilterOptionLists. |
placement | "bottom-start" | "bottom-start" | "bottom-end"Popover placement relative to the pill. |
label | — | stringFilter name shown on the pill. |
summary | — | ReactNodeCurrent-selection summary rendered next to the label (e.g. "All" or a count badge). |
isLoading | — | booleanToggles the loading state. |
isDisabled | — | booleanToggles the disabled state. |
isJustified | — | booleanTake up an entire available container width. |
size | — | "sm" | "md" | "lg" | "xl"Determines the height of the button. |
FilterPillButton
The pill trigger on its own, for a popover FilterPill can't host. Unlike FilterPill it accepts icon, for a pill that carries a leading glyph.
| Name | Default | Description |
|---|---|---|
label | — | stringFilter name shown on the pill. |
summary | — | ReactNodeCurrent-selection summary rendered next to the label (e.g. "All" or a count badge). |
isLoading | — | booleanToggles the loading state. |
isDisabled | — | booleanToggles the disabled state. |
isJustified | — | booleanTake up an entire available container width. |
size | — | "sm" | "md" | "lg" | "xl"Determines the height of the button. |
icon | — | ComponentType<SVGAttributes<SVGElement>>Primary icon that's displayed on the left.
Only icon or imageUrl can be set at the same time. |
FilterOptionList
The checkbox list (with "All" and a hover "Only" affordance) without a popover, so several filters can share one.
| Name | Default | Description |
|---|---|---|
label | — | stringRendered as a header above the list when provided. |
value | — | Set<T>Selected option values. |
onChange | — | (value: Set<T>) => voidCallback with the next selection when the user toggles options. |
options | — | FilterPillOption<T>[]Selectable options. |
showOnlyAffordance | true | booleanReveals a "Only" affordance on each row that narrows the selection to it. |