Clarity V2.0.1By Nivoda
ComponentsDisplay

FilterButton

Generic two-state control for applied filters. Owns per-popover draft state internally and exposes a render-prop `children` API so consumers write a controlled filter component without managing the draft lifecycle themselves.

Props

PropTypeDefaultDescription
labelstringFilter name, always visible (e.g. "Color").
chipSummarystring | undefinedundefinedFormatted display of the current applied value (e.g. "Blue, Green +3 more"). Shown in the active chip as label: chipSummary. Omit when inactive.
isActivebooleanWhen true, renders the active-state split button with label: chipSummary and an inline dismiss X. When false, renders the inactive single button with a trailing chevron.
initialValueV | undefinedSeeds the internal draft each time the popover opens. Typically the consumer's currently-applied value so that reopening the popover starts from the committed state.
popoverWidthnumber | stringOptional fixed width for the popover content (pixels or CSS length).
onApply(value: V | undefined) => voidCalled with the current draft value when the user clicks Apply. The popover closes automatically.
onClear() => voidCalled when the user clicks Clear. Internally resets draft to undefined. The popover closes automatically.
onDismiss() => voidCalled when the user clicks the inline dismiss X on an active chip. No popover interaction. Only rendered when isActive is true.
children(draft: V | undefined, setDraft: (v: V | undefined) => void) => ReactNodeRender prop for the filter control rendered inside the popover body, above Apply / Clear. Receives the current draft value and setter.
classNamestringExtra classes on the outer element.

Usage guidelines

FilterButton is the standard surface for any filter whose value can be applied from a popover — quick filters in a toolbar, engaged filters rendered inline, and any other context where a user edits or dismisses a filter without navigating away.

The component is generic over the filter value type V. Consumers declare the type at the call site: <FilterButton<string[]> ...>.

Draft lifecycle: each time the popover opens, draft is reseeded from initialValue. The draft mutates as the user interacts with the inner control. Clicking Apply commits draft via onApply; clicking Clear resets to undefined via onClear. Neither action survives a popover-open without an explicit Apply — reopening reseeds from initialValue again.

When to use: anywhere a filter's label + applied value + edit popover need to live in a single toolbar-height control.

When NOT to use: for non-filter actions (use Button), for multi-step filter workflows that don't fit in a popover (use a Sheet or dedicated page), or for read-only value displays (use Badge or a plain span).

Best practices

Do: Pass a concise, comma-joined chipSummary for multi-value filters. Truncate at two or three values with +N more rather than listing everything — long value summaries make the toolbar feel cluttered.

Do: Pass the currently-applied value as initialValue so the popover always opens from the committed state rather than stale or blank state.

Do: Provide onDismiss whenever isActive is true. Consumers should be able to clear the filter without opening the popover.

Don't: Mix inline icon adornments into the label — the label is plain text. If you need iconography, put it inside the render-prop content.

Don't: Use FilterButton for things that aren't filters. The label: value framing is specific to applied filter state.

Active vs inactive state

StateVisualInteraction
Inactive (isActive: false)Outline button with the label followed by a trailing chevron-down icon, matching Button variant="outline". The chevron signals that the button opens a popover.Click opens the popover. No dismiss affordance.
Active (isActive: true)Filled accent background. Split into two regions: label: value on the left (click to edit) and an X on the right (click to dismiss). No chevron — the dismiss X is the trailing affordance.Clicking the left region opens the popover; clicking the X calls onDismiss. Both share a unified focus ring.

Quality checklist

  • Accessibility: both regions are keyboard-focusable; dismiss X has aria-label="Remove filter: {label}"; popover inherits accessible name from the filter label
  • Responsive: no breakpoint-specific behaviour; consumers handle layout wrapping in the surrounding container
  • Tokens only: border/background/text styles use semantic utility classes; no raw colour or spacing literals

On this page