Skip to main content

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.

The solution

Filters and search compose into one small system rather than two separate ones: a discrete choice against a known field renders as a removable chip in a dedicated active-filters row; free-text search debounces before it queries; and the combined state — every filter, the search text — lives in the URL, so a narrowed view is exactly as linkable and durable as any other page. Structure below is that system’s concrete parts.

Vocabulary

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

Per Views overview, the search bar and filter controls sit in the page frame’s 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.
  • 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.

Known issues

Gaps in the current rules

  • Whether a filter combination should ever be savable as a named view the reader returns to, rather than reconstructed from the URL each time.
  • What happens when a filter’s own option list is itself long enough to need its own search — this page assumes a Select’s options are already short enough to pick from directly.
  • Whether search should ever match across more fields than it’s explicitly scoped to, and how that scope is communicated to the reader.

Why it works this way

Filters and search share one active-filters row because the reader doesn’t experience them as two systems. Whatever narrowed the list — a typed word or a chosen status — the question “why don’t I see everything” has one answer, and it should be answered from one place, not by checking a search box and a row of Selects separately. State lives in the URL because a narrowed view that can’t be linked or reloaded stops being a real view. This isn’t a convenience — a filtered list a reader can’t bookmark, share, or return to via the back button is functionally invisible to everything outside the single session that created it. Table and List, the two result-set components this pattern composes with. Views overview, for the Controls 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.

Status

Proposal — first pass, not an audited Invoca screen. No Titan design library or shipped product surface was available while writing this page. What follows is built from general interaction-design practice and from the constraints Foundations and Components already establish — see Status. One real, already-documented limitation is directly relevant here: TITAN-GAP-27 — pagination has no home in Titan’s page frame, and three unreconciled implementations exist. Since a filtered or searched result set is exactly the kind of list that needs pagination, this page builds against that gap’s own “Direction” note rather than re-deciding the question: pagination lives inside the table, at the bottom, above the page footer.
Last modified on September 17, 2026