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