# Filtering

How to choose the filters of a list, where they go, and how they apply, count, clear and stay visible.

Filters narrow a list to the records someone needs now: the critical alerts of one tenant, the
credentials leaked this week. This page says which filters a list gets, where they go and how
they behave. Build them with [Filter panel](/patterns/filter-panel); choose the columns of the
list with [Tables](/guidelines/tables).

## Choose the filters

- **Filter on what people decide by.** The deciding attribute of the table (severity, status,
  priority) and the context columns people narrow by (owner, source, type). A field nobody
  narrows by is not a filter, even if the API accepts it.
- **One control per kind of value.** Several values of a small set: a checkbox list. One value,
  or yes and no: a radio list. A long or open set (users, entities, tags): a search with chips.
  Values people type that no list holds (country codes, domains): free-text chips. A score or an
  amount: a floor, a ceiling or both; a threshold people pick from a few values (confidence 50,
  75, 90): a radio list of presets, never a slider of a hundred steps. An on/off condition (known
  exploited, internet-facing): a switch. Identifiers: the search box of the list, not a filter.
- **Time windows start with presets.** "Today", "Last 7 days", "Last 30 days", then
  custom dates. A preset stays relative in the URL, so a shared link still means the last seven
  days tomorrow; resolve it with today in the profile's time zone, never the browser's. Put a
  date filter in the panel only where the screen has no date window of its own.
- **Status tabs and search are not filters.** A tab splits the list into the views people switch
  between (open, resolved); the search finds a record. Neither has a section in the panel, and
  Clear never touches them.
- **Column visibility is not a filter.** It belongs to the Columns menu of the table.

### `zx/filter-place` Four or more facets go in a panel

- One to three facets: dropdowns in the toolbar, each a checkbox list in a Popover.
- Four or more, or any range or date: a [Filter panel](/patterns/filter-panel) on the inline
  start of the results, 240 px wide. It starts closed below a 1280 px window; people open it, and
  the app remembers their choice.
- Inside a detail sheet or a dialog, the panel never collapses (`collapsible="none"`).

## Counts

### `zx/filter-counts` A count says what a choice would return

- Show the number of results next to each value when the API can count them. People use the
  count to decide whether a filter is worth applying.
- Counts are disjunctive: a facet's counts take every other filter into account, never its own
  selection, so ticking a second value shows what it adds.
- Counts are exact ("1,204"), through the app's number formatter. Compact numbers are for Stat
  Tile and chart axes only.
- A count that ignores the filters (a global total) says so in the section description, or is
  left out.
- Keep the given order for ranked or small sets (severity, priority, status); sort by count for
  open sets (tags, sources). Hide values with no results only in open sets, and never a selected
  one.

## Apply and remember

- **Filters apply at once.** No Apply button on desktop. Text and numbers commit on Enter or
  when the field loses focus, never per keystroke or per step of a slider.
- **Queries wait for the user.** Debounce the request and cancel the one it replaces: arrow keys
  select radios as they move, and each move is a change.
- **Filters live in the URL.** Readable params (`?severity=critical&severity=high`), written with
  `filtersToSearchParams`, so a link reproduces the list. A filter change replaces the history
  entry instead of adding one per click.
- **Defaults are filters too.** A list that opens narrowed (recent only, pending) counts the
  default in the panel and shows it as a chip, and Clear becomes Reset.
- **Narrow layouts use a sheet.** Below 60rem the panel opens over the results from the same
  trigger. Filters still apply at once; the sheet's "Show 1,204 results" button takes people back
  to the list, and the chips summarize the filters while it is closed.

## Keep every filter in sight

### `zx/filter-visible` A filter in force is always visible

- With the panel open, the panel is the summary: each folded section shows how many values it
  holds.
- With the panel closed, chips under the toolbar list the active filters, one per field
  ("Severity: Critical, High"), each removable, with Clear filters at the end. The trigger shows
  the number of active filters.
- A filter without a section (from a shared link, a saved view, an advanced editor) always has a
  chip. A list narrowed by a filter nobody can see is a bug.
- One source of truth: when a list also has a query bar, the panel and the query show the same
  state.

## No results

### `zx/filter-zero` Zero results name their cause

When the filters, the search or the tab leave nothing, the empty state says which one, and its
action clears that one: "No alerts match these filters" with Clear filters, "No alerts match
'vpn-gw'" with Clear search. See [Empty states](/guidelines/empty-states).

## Review checklist

1. Every filter is something people decide by; tabs, search and columns are not filters.
2. Four or more facets, or a range or a date, use the panel; fewer use toolbar dropdowns.
3. Counts are disjunctive and exact; global counts say so.
4. Filters apply at once, debounced, and live in readable URL params.
5. Every active filter is visible: in the panel, or as a chip, defaults included.
6. Zero results name the cause and clear it.
