Skip to main content

Filtering

This guide covers how to control which cards appear on the board - from a single initial predicate to stacked, independently managed filters driven by external UI controls.

How filters work

Filtering is a view-level operation. The source card array stays intact; filters only control what the board renders.

The store keeps a Map<string, FilterPredicate> in state.filters. Each entry pairs a tag (a string you choose) with a predicate (a function that receives a card and returns true to keep it). When multiple filters are active, they stack with AND - a card must pass every predicate to appear on the board.

The projection pipeline applies filters before sorting. Drag-and-drop operates on the visible set; hidden cards stay in source order and reappear when filters are cleared.

Two things worth noting:

  • api.getCards() returns the full, unfiltered card array - the persistence snapshot.
  • api.getState().viewData reflects the filtered, sorted result the board actually renders.

Initial filter

Pass a filters prop to apply filters on first render. The value is a Map whose keys are tags and whose values are predicates:

import { Kanban } from "@svar-ui/react-kanban";

const filters = new Map([
["priority", card => card.priority === 3],
["mine", card => card.assignee === currentUser],
]);

<Kanban cards={cards} columns={columns} filters={filters} />

Both predicates apply from the start. Only cards that are high-priority and assigned to currentUser will be visible.

Changing the filters prop later replaces the entire filter map in the store. For interactive filtering, prefer dispatching filter-cards actions instead - that way you update one filter at a time without touching the rest of the board state.

Runtime filter changes

Use api.exec("filter-cards", ...) to add, replace, or remove filters at runtime. The tag field identifies which filter slot you're targeting.

Setting a filter

api.exec("filter-cards", {
tag: "search",
filter: card => card.label.toLowerCase().includes(term),
});

This creates (or replaces) the filter under the "search" tag. Other active filters are unaffected.

Stacking independent filters

Each tag is independent. Set multiple tags and they all apply:

api.exec("filter-cards", {
tag: "search",
filter: card => String(card.label ?? "").includes(term),
});

api.exec("filter-cards", {
tag: "priority",
filter: card => card.priority === 3,
});

Clearing "search" later leaves "priority" active.

Removing a single filter

Pass the tag without a filter (or with filter: null):

api.exec("filter-cards", { tag: "search" });

Clearing all filters

Dispatch an empty payload:

api.exec("filter-cards", {});

Integrating SVAR filter widgets

Kanban doesn't ship a built-in filter bar. You can wire any UI to filter-cards, but the @svar-ui/react-filter package provides ready-made controls that produce predicates from user input.

The filter widgets need their own theme wrappers (Willow / WillowDark from @svar-ui/react-filter), separate from the Kanban theme.

Field descriptors

All three filter widgets below accept a fields array that describes which card properties are searchable:

const fields = [
{ id: "label", label: "Label", type: "text" },
{ id: "description", label: "Description", type: "text" },
{ id: "priority", label: "Priority", type: "number" },
{ id: "column", label: "Column", type: "text" },
];

FilterBar renders a compact search input. Wire its onChange to dispatch a filter:

import { Kanban } from "@svar-ui/react-kanban";
import { FilterBar, createFilter } from "@svar-ui/react-filter";
import { useState, useRef } from "react";

const [api, setApi] = useState(null);
const fields = [{ id: "label", label: "Label", type: "text" }];

<FilterBar
debounce={0}
fields={fields}
onChange={({ value }) => {
api.exec("filter-cards", {
tag: "search",
filter: createFilter(value),
});
}}
/>
<Kanban cards={cards} columns={columns} init={obj => setApi(obj)} />

Board with a filter bar showing a text search input filtering cards

FilterBuilder - structured conditions

FilterBuilder lets users build multi-field filter rules through a UI:

<FilterBuilder
fields={fields}
type="line"
onChange={({ value }) => {
api.exec("filter-cards", {
tag: "builder",
filter: createFilter(value),
});
}}
/>

FilterQuery - query string with optional natural language

FilterQuery accepts structured query input. You can optionally send free text to a backend service that returns a filter JSON object:

<FilterQuery
value={textValue}
fields={fields}
onChange={async ({ value, text, error, startProgress, endProgress }) => {
if (text) {
startProgress();
try {
value = await textToFilter(text, fields);
} finally {
endProgress();
}
}

if (!error) {
api.exec("filter-cards", {
tag: "query",
filter: createFilter(value, {}, fields),
});
}
}}
/>

The natural-language endpoint is yours to build. Kanban doesn't parse free text - it only receives the final predicate through filter-cards.