Filter bar

Narrows a long list down to the rows someone came for.

Examples

Search, menus and a toggle

Conversations240

240 rows, sorted by Conversation descending

With a filter set

Conversations40
Agent eval-runner
cnv_8f2989eval-runnerRunning
cnv_8f2935eval-runnerIdle
cnv_8f2863eval-runnerWaiting on you
cnv_8f2839eval-runnerQueued
cnv_8f2791eval-runnerSlow
cnv_8f2767eval-runnerIdle
cnv_8f2713eval-runnerSolved
cnv_8f2695eval-runnerWaiting on you
cnv_8f2641eval-runnerSolved
cnv_8f2617eval-runnerSolved
cnv_8f2599eval-runnerIdle
cnv_8f2545eval-runnerSolved
cnv_8f2473eval-runnerSolved
cnv_8f2449eval-runnerSolved
cnv_8f2377eval-runnerSolved
cnv_8f295feval-runnerSlow
cnv_8f290beval-runnerSolved
cnv_8f288deval-runnerFailed
cnv_8f280feval-runnerSolved
cnv_8f273deval-runnerSolved
cnv_8f266beval-runnerQueued
cnv_8f256feval-runnerSolved
cnv_8f251beval-runnerSolved
cnv_8f249deval-runnerQueued
cnv_8f241feval-runnerRunning
cnv_8f29ddeval-runnerSolved
cnv_8f29b3eval-runnerSolved
cnv_8f28e1eval-runnerSolved
cnv_8f28b7eval-runnerSolved
cnv_8f27e5eval-runnerSolved
cnv_8f27bbeval-runnerRunning
cnv_8f26e9eval-runnerSolved
cnv_8f26bfeval-runnerFailed
cnv_8f25edeval-runnerRunning
cnv_8f25c3eval-runnerSlow
cnv_8f24f1eval-runnerFailed
cnv_8f24c7eval-runnerWaiting on you
cnv_8f23f5eval-runnerSlow
cnv_8f23cbeval-runnerIdle
cnv_8f23a1eval-runnerSolved

40 rows, sorted by Conversation descending

Applied filters

Status FailedAgent support-agentModel reasoning-large

Add filters

Choice

  • singledefault

  • multiple

Add filter

  • emptydefault

  • Channel: Chat

    set

Sizes

  • xs

  • defaultdefault

States

  • rest

  • hover

  • focus-visible

  • active

  • disabled

Usage

When to use it
Use
Above a list or table of 20 or more rows that people narrow by text and by known fields.
Use
The person needs to see how many rows are left at every step.
Use
Filters combine: a status and an agent and a model at once.
Not for
Switching between two to five fixed views of the same data. Use Segmented control.
Not for
Searching across the whole app, not one list. Use Command.
Not for
Picking a value that a form submits. Use Select.

Rules

12 rules
Must
Pass each option count from useFilteredRows, counted against the search, the other filters and pressed toggles. Because a count against the whole list promises rows the other filters already hide, so the pick empties the table.
Must
End the bar with FilterBarCount. Because its status region is the only thing a screen reader hears when the list changes.
Must
Give FilterBar an aria-label naming what it filters. Because FilterBar is a role="search" landmark, and unnamed ones read alike in the landmark list.
Must
Render FilterBarClear only while a filter or the search is set. Because a Clear all with nothing to clear is a dead tab stop that suggests a filter is still on.
Must
Keep shortcut="/" on the main bar and pass shortcut={null} to every other bar on the page. Because only the first bar on the page takes "/", so a second bar shows a hint for a key that focuses another field.
Should
Name a select with the column's one-word name; the chip adds the value.
Should
Name the noun in the search placeholder.
Never
Add an Apply button; apply each change as it happens. Because until the press the chips say one thing and the list another, and the count announces nothing.
Never
Hide filters behind a funnel icon button; put the two to four most used on the bar. Because a funnel hides which filters are set and adds a click before every pick.
Should
Use FilterBarAdd for a log or feed whose filters are mostly off, one value per field; use FilterBarSelect for a field that takes several values or shows counts. Because the dashed chip lists what can narrow the list without spending a row on it, but it can hold only one value.
Should
Keep 4 selects on the bar and move the rest behind a More filters menu.
Should
Pass the bar to DataTable's toolbar so it shares the table's frame.

Accessibility

6 checks
The bar is a search landmark; the search is a searchbox with aria-keyshortcuts="/".
A select chip is a button with aria-haspopup and aria-expanded; options are menuitemcheckbox or menuitemradio with aria-checked.
/ focuses the search outside fields, menus and dialogs, and not during IME composition. Escape clears the text.
Removing a chip moves focus to the next chip; after the last, to the select that set it.
Clear all moves focus to the search.
FilterBarCount is a polite status; the announced copy waits 500ms without change.

Edge cases

11 edge cases
Long select value
Truncates inside the chip; the full value is the accessible name.
Right to left
Search icon, clear and hint sit on the logical sides.
Text spacing
Chips widen, values truncate, the bar wraps.
Filters leave 0 rows
Count reads "0 of 240 conversations"; the list says why. Clear all in the bar is the one reset, so the empty row adds no Clear button.
Select with no options
Pass disabled instead of opening an empty menu.
Option count 0 and chosen
Stays in place so it can be unchecked.
Server-side search
The bar stays live; counts show the last result until the next arrives.
Failed fetch
Show Empty state reason="error" in the list; the bar keeps its values.
Typing / in a dialog, menu or open listbox
The shortcut does nothing there.
Forced colors
Chips take a 1px outline, pressed 2px.
Inside a DataTable toolbar
The strip is --row (40px) and spans the frame; cells drop their edges for the column rule.

API

18 props
FilterBar
div props
FilterBarChips row
boolean default false
FilterBarSearch value, onValueChange
string, (value) => void default required
FilterBarSearch shortcut
string | null default '/'
FilterBarSelect label, options
string, FilterOption[] default required
FilterBarSelect multiple
boolean default false
FilterBarSelect showValue
boolean default true
FilterBarSelect showAllOption, allLabel
boolean, string default false, 'All'
FilterBarSelect sinkEmpty
boolean default true with counts
FilterBarSelect size
'xs' | 'sm' | 'default' default 'default'
FilterBarAdd label, options
string, FilterOption[] default required
FilterBarAdd value, onValueChange
string | null, (value) => void default null
FilterChip label, value, kind, onRemove
string, ReactNode, Kind, () => void default required
FilterToggle
pressed, onPressedChange
FilterBarCount shown, total, noun
number, number, string default required
useFilteredRows
(rows, {search, match, fields, filters, where})
fields, filters
{id: string; value: (row) => string | string[]}[], Record<string, string[]>
match, where
(row, query) => boolean, ((row) => boolean | false)[]

Tokens

20 tokens
--surface-page
var(--paper)
--line-control
oklch(var(--ink-c) / 0.16)
--line-control-hover
oklch(var(--ink-c) / 0.32)
--line-row
oklch(var(--ink-c) / 0.08)
--surface-selected
oklch(var(--ink-c) / 0.09)
--control-sm
28px
--row
40px
--type-label
400 14px/16px var(--font-sans)
--text-3
oklch(var(--ink-c) / 0.66)
--radius-soft
6px
--radius-inner
4px
--radius-frame
10px
--elev-overlay
0 0 0 1px var(--line-frame), 0 12px 24px -12px oklch(var(--text-on-field-c) / var(--shadow-alpha))
--dur-base
160ms
--dur-fast
120ms
--ease-step
cubic-bezier(0.2, 0, 0, 1)
--surface-hover
oklch(var(--ink-c) / 0.05)
--type-label-sm
400 12px/16px var(--font-sans)
--type-body-sm
380 13px/18px var(--font-sans)
--text-1
var(--ink)

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.