The problem
A list grows past what someone can scan in one view, and two tools narrow it: filters, which offer a discrete choice against a known field, and a search box, which matches typed text against one or more fields. Both are deterministic — the same input against the same data produces the same result set every time, with nothing in between doing any interpreting. Get this wrong in one direction and the list is unusable past a few dozen rows, because nothing narrows it. Get it wrong in the other and the narrowing itself becomes invisible — a filter applied three clicks ago with no visible trace of it, so the person looking at nine rows instead of nine hundred has no way to tell why, and no way to undo it without knowing what to undo. This is not the same job as natural-language search. A user who already knows the field and the value they want — status, date range, campaign name — is choosing from a known, bounded set of options. A user who wants to describe what they’re after in their own words, without knowing which field holds it, needs something to interpret that request first. This page is the deterministic half of that fork; see When it doesn’t for the other half.When this applies
- The list is longer than fits in one view, and the reader needs a subset matching criteria they can name.
- The fields to filter or search on are fixed and known in advance — a status, a date range, a campaign, a tag, a name.
- The same input should always produce the same result set, with no interpretation step.
When it doesn’t
Structure
Controls region — they change what the body shows, never what
the underlying data is. That is also why filtering carries no confirmation step of its own: it
is never destructive, so Destructive confirmation’s
tiers do not apply here.
Behavior
Constraints
Content
Accessibility
- The result count is announced to a live region after a filter or search updates, once the update completes — not on every keystroke of a debounced search.
- Each filter chip’s remove control needs its own accessible name stating which filter it removes (“Remove filter: Status: Active”), not a bare ”×” with no context.
- An active filter chip’s state is never carried by color alone — pair it with the field name and value as text, per TITAN-COLOR-03.
- Focus stays on the search box or filter control the reader just used; a result-set update never steals focus to the results themselves. Moving focus away from an in-progress interaction (still typing, still choosing a filter) is more disruptive here than in a one-shot action like a completed search.
Variations
Anti-patterns
Filters that change the result set with no visible trace. A Select that narrows the table but leaves no chip and no indication anything is active is the single most common failure here — the reader eventually notices rows are missing and has no way to find out why. Debouncing so long the search feels broken, or not at all. No debounce means the table reflows on every keystroke; a debounce measured in seconds reads as the search not working. Neither extreme respects the reader’s sense that typing should produce something. Treating “nothing matched” the same as “nothing exists.” A search or filter that returns zero rows is not the same message as a list with no data in it at all — the distinction belongs to Empty & zero states, and collapsing the two into one generic “no results” message loses information the reader needs to decide what to do next. A “clear all” that only clears some of the state. If search text survives a “clear all” that visibly cleared every filter chip, the reader has no reason to expect the list is still narrowed.Related
Table and List, the two result-set components this pattern composes with. Views overview, for theControls
region this pattern fills. AI Experience: Search,
the non-deterministic counterpart for requests that don’t map onto known fields.
Empty & zero states, for the zero-result
case this page hands off to.