---
title: Filter Bar
slug: filter-bar
kind: pattern
summary: A row of controls above a collection that narrows it by attribute, with every active filter visible as a removable chip and the whole state carried in the URL.
problem: >-
  Someone wants "all overdue invoices owned by Dana" — they know the attributes,
  not the name. Search cannot help, because there is no term to type. Without
  filters they scan the whole list, and without visible active filters they
  eventually forget what they are looking at.
family: [find, scan]
data_shape: [collection, tabular]
principles: [friction, orientation, progressive-disclosure]
interaction: [filtering, selection]
density: medium
complexity: medium
status: stable
visibility: public
use_when:
  - People describe what they want by attributes rather than by name.
  - The collection is too large to scan and too varied for one default view.
  - The same few narrowings are used repeatedly by the same people.
avoid_when:
  - There are fewer than about fifteen records. Sorting is enough.
  - Only one attribute ever matters — then it is a set of tabs, which is cheaper and more visible.
  - Every user needs a different combination every time; consider saved views instead.
alternatives:
  - slug: global-search
    when: People know the name or the identifier of the thing they want.
  - slug: saved-views
    when: The same combinations are rebuilt daily and deserve a name.
ask_leo: |
  Put a filter bar above this collection.

  - Offer filters for the attributes people actually narrow by. Do not add one
    per column; every filter is a control everyone reads and most never use.
  - Show every active filter as a removable chip, with its value, next to the
    controls. A filter that is on but not visible is how people end up
    misreading a list.
  - Include a single "Clear all" that appears only when something is active.
  - Put the whole filter state in the URL so a view can be reloaded, bookmarked
    and pasted to a colleague, and so returning from a record restores it.
  - Show the result count, and update it as filters change.
  - When a combination matches nothing, say which filters are active and offer
    to clear them. Never show a bare empty list.
  - Keep sensible defaults visible rather than hidden: if the list defaults to
    the current month, show that as a chip like any other filter.
related:
  - title: Filterable Index with Slide-Out Detail Drawer
    url: /cookbook/filterable-index-with-detail-drawer
    summary: The implementation, filter bar and drawer together.
  - title: Dense Operational Table
    url: /patterns/dense-operational-table
    summary: What sits below the bar, and why fewer columns beats more filters.
---

## Anatomy

```
┌─────────────────────────────────────────────────────────────┐
│ Status ▾   Owner ▾   Date ▾            42 of 318 invoices   │
│ ● Overdue ✕   ● Owner: Dana ✕                  Clear all    │
└─────────────────────────────────────────────────────────────┘
   ↑ active filters, visible and individually removable
```

The controls are the easy half. The three that matter:

1. **Chips for what is on.** The single most common failure is a filter applied
   from a dropdown that then shows no trace of itself. People read a partial
   list as if it were the whole one and draw wrong conclusions.
2. **State in the URL.** It makes the view shareable, reloadable and — the part
   people feel every day — restorable when they come back from a record.
3. **A count.** "42 of 318" is orientation: it says both how much you have and
   how much you excluded.

## Why it works

It serves a different question from search. Search answers *where is this
specific thing*; filters answer *which of these things match a description*. A
product that offers only one of the two leaves half its users scanning.

Chips are what make it safe. A filtered list looks exactly like an unfiltered
one, so the interface has to say which it is — otherwise the person who inherited
someone else's saved link reports a data problem that is really a filter.

Putting the state in the URL is the quiet upgrade. It turns a transient UI state
into something a person can send in a message, which is how most real
collaboration on a list actually happens.

## Getting it wrong

- **Invisible active filters.** The list is wrong and nothing on screen says so.
- **A filter per column.** Filters are a shared cost: everyone reads them, few
  use each one. Add the ones people ask for, not the ones the schema permits.
- **Filter state only in memory.** Reload, or come back from a record, and the
  work is gone — see [back link](/patterns/back-link).
- **A bare empty state.** "No results" with active filters and no way to clear
  them is a dead end.
- **A hidden default.** A list quietly scoped to this month, with nothing saying
  so, produces confident wrong answers.

## Exemplars

**Linear's filter bar** builds up as chips, each one editable in place, and the
whole set is one click from being saved as a view. It shows the natural
progression: filters that keep getting rebuilt want a name.

**Stripe's payments filters** are notable for restraint — a handful of
attributes on a record type with dozens of fields — and for putting everything
in the URL, so support conversations happen by pasting a filtered link.

The extractable rule: **a filter bar's job is to make the current scope
undeniable.** Narrowing the list is the easy part; saying so, permanently and
visibly, is what keeps people from misreading it.
