A pill that turns one filter on and off, such as "Delays" in the top bar's filters, with an optional icon before its words and a count after them, drawn in ink when it is on.
When to use
Use FilterChip for a filter people turn on and off on its own, in a row of filters, such as showing delays on the map.
| Use instead | When |
|---|---|
| SegmentedControl | The choice is one of two to five views or modes, and only one holds at a time |
| HighlightChip | One highlight floats over the map, such as a line in focus |
| Switch | The thing turned on and off is a setting, which takes effect at once |
| Tag | A property is marked and can't be pressed |
Kongens Nytorv
Metro M1, M2 and M3.
Variants and states
| Rest | Hover | Pressed | Focus | Disabled | |
|---|---|---|---|---|---|
| Default |
- Rest
- Hover
- Pressed
- Focus
- Disabled
With icons
An icon before the label, in reading order, such as the disruption icon of the kind the chip filters. Hidden from screen readers: the label names the chip.
Props
| Name | Type | Default | Description |
|---|---|---|---|
selected | boolean | — | Whether the filter is on, when the caller controls it. Announced as pressed. |
defaultSelected | boolean | false | Whether the filter is on at first, when FilterChip keeps the state itself. |
onChange | (event: Event, value: boolean, details: Toggle.ChangeEventDetails) => void | — | Called when a click, Enter or Space turns the filter on or off, with the new state as value; details.reason is 'none', and details.cancel() keeps the state as it was. event is details.event. |
disabled | boolean | false | Whether the chip ignores the pointer and the keys. A disabled chip leaves the tab order and keeps its state. |
startIcon | ReactNode | — | An icon before the label, in reading order, such as the disruption icon of the kind the chip filters. Hidden from screen readers: the label names the chip. |
count | number | — | How many the filter matches, shown after the label as a Count, such as the "3" of "Delays 3". Heard after the label. Shown for 0 too; left out when undefined or not a finite number. |
children | ReactNode | — | The label: the name of what the filter shows, in sentence case, such as "Delays". It stays the same whether the filter is on or off. |
className | string | — | Reaches the root. className is merged after pn-FilterChip. |
style | CSSProperties | — | Reaches the root. className is merged after pn-FilterChip. |
ref | Ref<HTMLButtonElement> | — | Reaches the root. className is merged after pn-FilterChip. |
selectedboolean, default —- Whether the filter is on, when the caller controls it. Announced as pressed.
defaultSelectedboolean, defaultfalse- Whether the filter is on at first, when FilterChip keeps the state itself.
onChange(event: Event, value: boolean, details: Toggle.ChangeEventDetails) => void, default —- Called when a click, Enter or Space turns the filter on or off, with the new state as
value;details.reasonis'none', anddetails.cancel()keeps the state as it was.eventisdetails.event. disabledboolean, defaultfalse- Whether the chip ignores the pointer and the keys. A disabled chip leaves the tab order and keeps its state.
startIconReactNode, default —- An icon before the label, in reading order, such as the disruption icon of the kind the chip filters. Hidden from screen readers: the label names the chip.
countnumber, default —- How many the filter matches, shown after the label as a Count, such as the "3" of "Delays 3". Heard after the label. Shown for
0too; left out whenundefinedor not a finite number. childrenReactNode, default —- The label: the name of what the filter shows, in sentence case, such as "Delays". It stays the same whether the filter is on or off.
classNamestring, default —- Reaches the root.
classNameis merged afterpn-FilterChip. styleCSSProperties, default —- Reaches the root.
classNameis merged afterpn-FilterChip. refRef<HTMLButtonElement>, default —- Reaches the root.
classNameis merged afterpn-FilterChip.
Use it
import { FilterChip } from '@mylder/perron'Ask your agent
get_reference({ library: "base-ui", component: "Toggle" })Keyboard and ARIA
From the Base UI reference for Toggle and the installed Toggle and useButton. "From" says who does it.
| Key | Does | From |
|---|---|---|
Tab, Shift+Tab | Moves focus to the chip and away; a disabled chip is skipped | The browser, and Base UI's native disabled |
Enter | Turns the filter on or off, through the button's click, and calls onChange with the new state | The browser; Base UI for a non-button render |
Space | Turns the filter on or off on release, through the button's click, and calls onChange with the new state | The browser; Base UI for a non-button render |
FilterChip adds no keys of its own. A row of chips is a tab stop each, not one composite stop: they are separate filters, not one choice.
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | button, native, a toggle button by its aria-pressed; role="button" from Base UI when render gives a non-button | Its text content: the label's children, then the count, as "Delays 3"; the caller's aria-label or aria-labelledby win | aria-pressed, true or false; the native disabled |
startIcon | none: aria-hidden="true" | none | none |
count | none: text inside the chip, Count's generic <span> | Part of the chip's name | none |
What axe won't catch, from the contract, each with how this component meets it:
- Everything the eye is told, the ear is told, for every shape each prop accepts: the selected fill is
aria-pressed, heard as "pressed" or "not pressed" after the name; the count is text in the name, heard after the label; the icon isaria-hidden, since it sits beside the word it shows (Icons); a disabled chip's tertiary colour is the nativedisabled. - Every landmark and named region has an accessible name: none here; a chip is neither. The group of chips that holds them is the pattern's, which names it.
- Every default label can be overridden: none here; FilterChip owns no strings.
- Every shape the type accepts renders, in every variant:
childrenandstartIconrender a string, an element and a list alike, on and off and disabled;countrenders0and larger numbers alike, and nothing forundefined,NaNorInfinity. - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: none here; FilterChip keeps no hidden copy and collapses nothing. - Native semantics first: a native
<button>witharia-pressedfrom Base UI'sToggle; withrenderandnativeButton={false}, Base UI'srole="button". - Focus moves in reading order and returns on close: each chip is one tab stop in document order, focus stays on the chip it toggles, and it opens nothing to return from.
- Every target is at least
size.target-minsquare: the smallest target is a chip with a one-word label and no icon or count,size.control(34 px) tall and the word plusspace.300on each side wide. - Loops stop when the system asks for less motion: none here; FilterChip has no loop and no transition.
Owned strings
No owned strings: every word comes from the caller, as children, the count, or an aria-label. FilterChip renders its Count only for a finite number (CR-9), so Count's own "Unknown" never shows inside a chip.
Do and don't
For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | must | Use FilterChip for a filter people turn on and off on its own, such as showing delays on the map | Use it to pick one of several views or modes: that is one choice, a SegmentedControl's, and chips would let two be on at once | review |
| DD-2 | must | Keep the label the filter's name, the same on and off, in sentence case: "Delays", "Planned works" | Change the label to say the state ("Delays on"): aria-pressed and the fill already say it, and a changing label moves the row | review |
| DD-3 | must | Give the count as count, so every chip's count has Count's shape and is heard after the label | Build a count into children, or pass a guess when the number is unknown: leave count out, and the chip shows none | review |
| DD-4 | should | Use the content design words for kinds of disruption and data states, so the chips agree with the rest of Mylder | Coin a filter name of your own for a kind the table names | review |
| DD-5 | must | Lay a row of chips out with flex-wrap: wrap and a gap, and name the group in the pattern that holds it | Put chips in a Toolbar or make them one tab stop: each is a filter of its own, reached with Tab | review |
| DD-6 | must | Use a Tag to mark a property that can't be pressed | Use a FilterChip as a label: a chip invites a press, and one that does nothing misleads | review |
| DD-7 | must | Disable a chip only while its filter can't apply, and say why nearby | Disable a chip to show a count of 0: show the 0, since a disabled chip leaves the tab order and can't explain itself | review |
| DD-8 | must | Keep onChange for the filter's state, and cancel it with details.cancel() when the change must not happen | Set aria-pressed yourself or toggle a class: Base UI keeps the state and the attribute in step | review |