frontend engineering

Build a Search Filter Bar with Multiple Filter Types

Filter bars appear on every catalog, dashboard, and search interface. Linear, Notion, GitHub, and every B2B SaaS has them. The interview tests whether you understand the data model, URL state, and the UX details that make filter bars feel powerful or tedious.

Functional requirements

  • Multiple filter types: text, range, multi-select, date range. Each type needs its own value shape and its own editor, so interviewers watch whether you unify them under one filter model instead of hard-coding a separate component per type.
  • Active filters visible (chips above the list). Applied filters should sit above the results so users can see and undo their state at a glance; hiding active filters behind a menu is the most common complaint.
  • Clear individual filter or all. Every chip needs its own remove control plus a single “Clear all”, and clearing must also update the URL and refetch — not just wipe local state.
  • Filters persist in URL. Reload and the browser back button should restore the exact same filtered view; this is the detail that separates a real implementation from a toy.
  • Filter chip + popover UX (Linear-style). Clicking a chip opens a small editor in place rather than sending the user off to a separate settings panel.

The data model

type FilterValue =
  | { kind: 'text', value: string }
  | { kind: 'select', values: string[] }
  | { kind: 'range', min: number, max: number }
  | { kind: 'date', from: Date, to: Date };

type Filters = Record<string, FilterValue>;

URL state

Encode filters in URL for shareability:

?status=open,in_progress&assignee=alice&created_after=2026-01-01

Or for complex filters, JSON-encoded:

?filters=eyJzdGF0dXMi...

Library: nuqs handles typed query-param state.

Filter chip pattern (Linear-style)

Each active filter renders as a chip with:

  • Filter name (“Status”) — the field being constrained, so users know what the chip controls at a glance.
  • Operator (is, is not, contains) — shown inline so the chip reads like a sentence; keep the operator set consistent across similar field types.
  • Value(s) (“Open”) — the current selection, truncated with a “+2” overflow indicator when several values are active.
  • Click → popover for editing — clicking anywhere on the chip body reopens the same editor used to create it, so editing and adding share one code path.
  • X to remove — a dedicated hit target on the right that deletes just this filter and refetches.

Add filter button: dropdown of available filter types.

Filter editor popover

For a multi-select filter:

  • Search input at top — lets users find an option in a long list without scrolling; filter the options as they type.
  • List of options with checkboxes — show current selections as checked and keep the popover open on each toggle so users can pick several at once.
  • Apply / Clear buttons — Apply commits the selection to the filter and the URL; Clear resets only this filter’s values, not the whole bar.

For a range filter:

  • Min and max input fields — accept typed bounds and validate that min ≤ max before applying, swapping or blocking when they cross.
  • Slider for visual selection — a two-handle slider gives quick approximate ranges; keep it synced with the number inputs in both directions.

For a date filter:

  • Calendar with range selection — click a start date then an end date, and highlight the span between them so the selection is obvious.
  • Preset shortcuts (last 7 days, last month) — presets cover the most common asks in one click and set an explicit from/to underneath, so the URL stays a concrete date range.

Server-side vs client-side

For small datasets: filter client-side (instant feedback).

For large datasets: filter server-side (URL → API call → results).

Hybrid: filter applied server-side; client retains some derived filter state.

Top-level search bar typically separate from filters:

  • Free-text search — matches across the main text fields such as title and description; decide up front whether it runs client-side or hits the API.
  • Search-as-you-type with debounce — wait ~250ms after the last keystroke before firing so you send one request per pause instead of one per character.
  • Persists in URL alongside filters — treat the query text as one more URL param so a shared link reproduces both the search and the active filters.

Saved views

Power-user feature: save filter combination as named view (“My open tasks”).

  • Stored in user profile — persist the filter set server-side so the view follows the user across devices.
  • Shareable URL — a saved view is just a named URL, so sharing it hands someone the exact same filtered list.
  • Default view per user — let a user pin one view as the landing state so the list opens pre-filtered instead of showing everything.

Performance

  • Debounce text filter changes (200–300ms)
  • Cancel in-flight requests when filters change — use an AbortController so a stale response can’t overwrite results for the filters the user has since changed.
  • Optimistic UI: dim list while loading

Empty states

  • “No results” with clear filters CTA
  • Suggest broadening (remove filters) — point at the most restrictive filter and offer to drop it, rather than a generic “try again”.
  • Differentiate “no results” from “still loading”

Accessibility

  • Filter chips are keyboard-navigable — chips should be reachable by Tab, editable with Enter, and removable with Delete or Backspace.
  • Filter editor popover has focus trap — move focus into the popover on open, keep Tab cycling inside it, and return focus to the chip on close.
  • Active filter count announced via aria-live — a polite live region that says “3 filters applied, 42 results” after each change keeps screen-reader users in sync.
  • Filters describable for screen readers — each chip needs an accessible label that reads the field, operator, and value together, not just the visible value.

Common mistakes

  • Filters not in URL (refresh loses state)
  • Slow filter response (no debounce or no skeleton)
  • Inconsistent operator support (some filters allow “not”, others do not)
  • Mobile UX broken (filter editor too cramped)

Frequently Asked Questions

Linear vs traditional filter bar?

Linear-style chips are more compact and discoverable for power users. Traditional sidebar filters work better for browse-first UX.

How do I handle complex boolean filters (AND, OR, NOT)?

Most apps stick to AND between filters; OR within a filter (e.g., status = Open OR In Progress). Full boolean is rare; usually a flag for power users.

Should I implement my own or use a library?

For typical use cases, build it. The patterns are stable and customization usually outweighs library savings.

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

1972 Soviet postage stamp commemorating the Mars 2 probe

worth a read

Mars For The Rest of Us — a weekly-or-more deep dive on the technical side of Mars exploration: rocket propulsion, microbiology, mission architecture, and everything in between. Written by Maciej Ceglowski.

Read it on Substack →
Scroll to Top