Filter bar
Narrows a table, a log or a list in one row.
Part
bardefault
Filter
selectdefault
select, set
multiple, set
toggle
toggle, on
States
rest
hover
focus-visible
set
several set
disabled
Usage
When to use it
- Use
- Above a table or a list of jobs, partitions or runs longer than a screen, that a person narrows by text and by a few fields.
- Use
- In a Table's toolbar row, where it fills the row.
- Not for
- One search over a short list, with nothing else to narrow by. Use Input.
- Not for
- Switching between two to five views of the same thing. Use Segmented control.
Rules
7 rules
- Must
- Keep the bar to one row of 26px controls: the search, then the filters, then the count and Clear. Because every control at one height with one 1px edge reads as one ruled row; a 32px field or a second row of chips beside it breaks the line.
- Never
- Put a filter bar over a list that fits on one screen. Show one only once the rows run past a screen. Because six rows are read faster than they are filtered, so a search and two filters above them are a row of chrome that pushes the rows down.
- Never
- Put a primary Button in the bar, or style a set filter as one. Because a row of filters has no lead, and in dark a tint with a yellow line is the primary button, so a filter drawn that way reads as the action.
- Must
- Show the count with FilterBarCount, "128 of 1,204 jobs", whenever a filter can narrow the list. Because the count says the filters worked, and its status region tells a screen reader once typing pauses.
- Should
- Pass each option a count from useFilteredRows. Because the counts are taken against every other filter, so an option that would empty the list sinks under "None left" before it is picked.
- Must
- Give options their values as written: failed, eu-west-2, events_daily. Because the field is the label in capitals and the value is data; a capitalised region or table name is a different string from the one in the rows.
- Should
- Render FilterBarClear only while something is set. Because a Clear with nothing to clear is a dead control, and it hands focus back to the search when it goes.
State order
6 states, highest first
- 1
- disabled
- 2
- open
- 3
- set
- 4
- focus-visible
- 5
- hover
- 6
- rest
Accessibility
6 checks
- The bar is role="search", named by aria-label.
- A filter's name says its value: "Status: failed, queued"; the caret is decoration.
- A quick filter is a toggle with aria-pressed; the selection wash is never the only sign, the name says what it does.
- The count speaks once through a status region, 500ms after typing pauses.
- Focus is Sodium's 2px gold ring 2px out on each cell; an open menu turns the cell white with its edge --focus doubled inward and a pale halo, as a field with the cursor in it.
- In the menu the highlighted option lifts to white between two hairlines and a chosen one carries an ink check, no fill.
Keyboard
4 keys
- /
- Focuses the search from anywhere outside a field, dialog or menu.
- Escape
- In the search, clears it; in a menu, closes it.
- Enter or Space
- Opens a filter's menu, or turns a quick filter on or off.
- Arrow keys
- Move through a menu's options.
Edge cases
6 edge cases
- Narrow bar, under 28rem
- The search takes its own row; the filters, count and Clear go under it.
- Very narrow, under 18rem
- The count is only heard, not shown.
- Long value
- The cell ends its value in an ellipsis; the name keeps the whole value.
- Several values chosen
- The cell shows how many; the name lists them.
- Option that would empty the list
- Sinks below a rule under "None left with the other filters" and cannot be picked.
- Dark theme
- A set filter takes the dark yellow tint under text-1 with its rule edge, never a yellow line.
API
10 props
FilterBardiv propsFilterBarSearch value, onValueChangestring, (value) => voidFilterBarSearch shortcutstring | nulldefault'/'FilterBarSelect label, optionsstring, FilterOption[]FilterBarSelect multiplebooleandefaultfalseFilterBarSelect showAllOption, allLabelboolean, stringdefaultfalse, 'All'FilterToggle pressed, onPressedChangeboolean, (pressed) => voidFilterBarCount shown, total, nounnumber, number, stringFilterBarClearButton propsdefault'Clear'useFilteredRows(rows, {search, match, fields, filters, where}) => {rows, counts}
Tokens
24 tokens
--control-sm- 26px
--field-bg- #f8f7f3
--field-hover- #fefefd
--field-edge- var(--line)
--field-edge-hover- #ecebe5
--field-ring-hover- #f4f3ee
--field-focus-bg- #ffffff
--field-ring-focus- #fff1b8
--field-inset-sm- 7px
--row-highlight- #ffffff
--line- #e2dfd5
--focus- #7a5d00
--selected- #fff4bf
--selected-strong- #ffec99
--well- #f4f3ee
--text-1- #161512
--text-2- #55524a
--text-3- #6b675e
--text-disabled- #a8a499
--type-label-sm- 400 12px/16px var(--font-mono)
--type-code-sm- 400 12px/16px var(--font-mono)
--type-caption- 400 13px/18px var(--font-sans)
--icon-sm- 14px
--dur-state- 120ms