Set filter

A "set filter" (a.k.a. value filter, list filter) shows a checklist of all distinct values in a column and lets the user pick which to include. It's what you reach for to filter status to "active OR pending", or department to a few specific teams.

Three patterns are supported, all wired through the imperative api.setFacetFilter(columnId, values | null):

Mode UI Built-in? When to use
Excel-style Column-menu Values tab Yes Most columns. Distinct values are enumerated from the loaded data; search box + select-all + clear ship out of the box.
Async Side panel that loads values from a server endpoint User-land (one screen of code) The column has too many distinct values to list at full data load. Lazy-fetch on demand.
Tree-list Hierarchical checkboxes (parent ↔ descendants) User-land (cascade logic) Nested taxonomies: Region → Country → City; Department → Team → Employee.

1. Excel-style (built-in)

The column menu's Values tab is the default set filter. Click the funnel icon on any header to open it.

What you get without writing any code:

Programmatic equivalent for "remember and restore":

// Capture the current set
const filters = api.getFilters() // includes selectedValues per column

// Restore later (e.g. saved view, URL persistence)
api.setFacetFilter('status', ['active', 'pending'])
api.setFacetFilter('status', null) // clear

2. Async values (server-loaded)

When a column has tens of thousands of distinct values, you don't want to pre-render them all. Pattern: render a side panel beside the grid, load values from the server on first open, drive the grid via api.setFacetFilter:

let state = $state<{
  status: 'idle' | 'loading' | 'ready' | 'error'
  values: string[]
}>({ state: 'idle', values: [] })
let selected = $state<Set<string>>(new Set())

async function loadValues() {
  state = { state: 'loading', values: [] }
  const res = await fetch('/api/orders/customers')
  const values = await res.json()
  state = { state: 'ready', values }
}

function toggle(value: string) {
  const next = new Set(selected)
  if (next.has(value)) next.delete(value); else next.add(value)
  selected = next
  api.setFacetFilter('customer', next.size === 0 ? null : Array.from(next))
}

Key benefits vs the Excel tab:

See demo 111 ("Async values" card) for a complete implementation with loading state, retry, and search.

3. Tree-list (hierarchical)

For nested taxonomies, render a tree of checkboxes. Parent checked = all descendants checked. Some descendants checked = parent shows the "indeterminate" state. On any change, compute the leaf set and apply it to the column.

// Taxonomy: Region → Country → City
const GEO = {
  Americas: { 'United States': ['New York', 'San Francisco'], Canada: ['Toronto'] },
  EMEA:     { Germany: ['Berlin', 'Munich'], France: ['Paris'] },
}

let selectedCities = $state<Set<string>>(new Set())

function toggleNode(node: TreeNode, on: boolean) {
  const next = new Set(selectedCities)
  for (const city of node.cities) {  // pre-computed leaf set
    if (on) next.add(city); else next.delete(city)
  }
  selectedCities = next
  api.setFacetFilter('city', next.size === 0 ? null : Array.from(next))
}

function isChecked(node: TreeNode): boolean {
  return node.cities.every((c) => selectedCities.has(c))
}
function isPartial(node: TreeNode): boolean {
  const hits = node.cities.filter((c) => selectedCities.has(c)).length
  return hits > 0 && hits < node.cities.length
}

The grid is unaware of the hierarchy - it just receives a flat list of allowed leaf values. The hierarchy lives in your panel UI.

See demo 111 ("Tree" card) and demo 102: Tree checkbox cascade for the cascade-logic recipe.

API surface

type SvGridApi<…> = {
  // Set a multi-select set filter. Pass null or [] to clear.
  setFacetFilter(columnId: string, values: ReadonlyArray<string> | null): void

  // Read the current filters (includes the facet selection per column).
  getFilters(): Record<string, { operator: ..., selectedValues?: string[] }>

  // Snapshot of the rows the grid currently displays - useful when your
  // filter UI needs to count matches without re-running the search.
  getDisplayedRows(): ReadonlyArray<TData>
}

See also