Light

Filtering

Guidelines and decisions for implementing consistent, accessible filtering experiences across products.

Overview

Filtering is one of the most common interaction patterns in software. When done well, it empowers users to take control of large datasets and complex content. When done poorly, it creates confusion, dead ends, and frustration.

When to Use Filtering

  • A data set or content list is large enough that scanning is impractical
  • Users have known criteria they want to apply (e.g., date, status, category)
  • Content has structured attributes that can be used as filter dimensions

When NOT to Use Filtering

  • The dataset is small enough to scan visually (< ~15 items)
  • Users are exploring without a specific goal — consider sorting or grouping instead
  • The only meaningful filter is a text search — use a standalone search input

Behavior & Layout

These rules define how filtering experiences should behave across all products. Each subsection is an independently referenceable decision that product teams must follow when implementing filters.

Placement

Filter controls must be inline with the header of the content section they control.

Filters should sit in the same row as — or directly below — the heading of the section they affect. They should never appear in a disconnected global toolbar when they only control a local section. On pages with multiple filterable sections, each section owns its own scoped filter controls.

  • Do: Place filter controls next to the "Visits" heading in a Visits table section.
  • Don't: Place filters for a single table in a shared app-level toolbar.

Progressive Disclosure

When there are 3 or fewer filter controls, display them visibly by default. When there are more than 3, hide them behind a "Filter" button.

This prevents filter-heavy views from overwhelming the interface while keeping simple filter sets immediately accessible.

  • 3 or fewer filters: Render all filter controls inline in the header row. No toggle or container needed.
  • More than 3 filters: Show a "Filter" button (with a ListFilter icon) in the header row. Clicking it opens an expandable container below the header that holds all filter fields in a grid layout.
  • The container pushes content down — it must never overlap or cover the content area below it.
  • When filters are applied and the container is collapsed, display an active filter count badge on the trigger button (e.g., "Filters (3)") so users know filtering is in effect.

Real-time vs. Deferred Application

Use real-time filtering for small client-side datasets. Use an explicit "Apply" button for large or server-fetched datasets.

  • Small-to-medium datasets (roughly < 500 rows, client-side): results update immediately as users change filter values.
  • Large or server-fetched datasets: show an explicit "Apply Filters" button inside the filter container. Disable the button when the current selection matches the already-applied state.
  • Always show a loading or skeleton state while filtered results are being fetched.

Default State & Presets

The default filter state should show all content (no filters applied) unless the product context demands a pre-filtered view.

  • When a pre-filtered default is used (e.g., "My Items" in a shared workspace), make it visually obvious that filtering is active — show the filter pill and count badge even in the default state.
  • Consider offering saved/preset filter combinations for power users who repeatedly apply the same criteria.

Filter Persistence

Persist filter state in URL query parameters so filtered views are shareable, bookmarkable, and survive page refresh.

  • On SPA back-navigation, restore the previous filter state from the URL.
  • "Clear all filters" must also clear the filter-related query parameters from the URL.
  • Filter state in the URL should be human-readable where possible (e.g., ?status=active&type=report).

Responsive Behavior

On mobile and narrow viewports, filters are always hidden behind a "Filter" button — regardless of how many filters there are. Filters open in a Sheet component instead of a push-down container.

Even if there are only 1–3 filters, mobile viewports do not have space to render them inline. The pattern changes at narrow breakpoints:

  • All filter controls move into a bottom Sheet (drawer) triggered by a "Filter" button in the header row.
  • Inside the Sheet, filter fields are stacked vertically in a single column.
  • "Clear all" and "Apply" actions are fixed at the bottom of the Sheet.
  • The Sheet must trap focus and support Escape / swipe-down to close (see Accessibility).

Dependent / Cascading Filters

When one filter selection narrows the available options for another filter, update the dependent filter's options immediately.

  • If selecting "Math Department" limits the Course filter to only math courses, the Course dropdown should update its options in real time.
  • If a previously selected value in the dependent filter becomes invalid after the parent changes, clear the dependent filter and notify the user (e.g., a brief inline message or toast).

Empty State & Zero Results

Show a distinct empty state when filters produce zero results, separate from "no data exists."

  • The empty state should suggest actionable next steps: clear specific filters, clear all filters, or broaden criteria.
  • Never remove filter options that yield 0 results. Instead, disable them and show a "(0)" count so users understand why results are limited.
  • If all filter categories yield 0 results, surface the "Clear all filters" action prominently.

Sort vs. Filter Distinction

Sort and filter controls must be visually and spatially separated. Never combine them into a single control.

  • Convention: filters on the left (or in the header row), sort controls on the right.
  • Sorting changes the order of results; filtering changes which results appear. Conflating them confuses users.

Accessibility

  • All filter controls must be keyboard-navigable and operable without a mouse
  • Use aria-label or aria-labelledby on filter groups so screen readers announce the filter category
  • When filters update results, announce the new result count using an aria-live="polite" region
  • Chip/toggle filters must have clear selected/unselected states conveyed to assistive technology (use aria-pressed)
  • Filter panels opened as overlays (Sheet/Drawer) must trap focus and support Escape to close
  • "Clear all filters" and "Apply" actions must be reachable via keyboard without tabbing through every filter option

See Also


Last updated: February 27, 2026