Filter pill
Filter pill is a rounded trigger that opens a popover of checkbox options and summarizes the current selection ("All" or a count) on the pill 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 pill when it should read as an applied filter.
Example
FilterPillSelect is the batteries-included form: one pill, one checkbox list, an All-or-count summary.
Usage
Selection and the "All" row
The pill reads "All" only for a full selection, and a count otherwise — a count of "0" included, 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 anything short of 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.
The "Only" button is the keyboard and screen-reader path to narrowing a filter to one option. Clicking anywhere on a row does the same thing, but 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
FilterPillSelect
A FilterPill with a single checkbox FilterOptionList and an All-or-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. |
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. |