Excel-style filters
Per-column operator dropdown with active-filter chips and clear.
A live, editable Svelte 5 data grid example from the SvGrid gallery (Filtering & Search). See the SvGrid documentation for the full API.
About this example
Excel-style column filters in the Svelte 5 data grid. Every header's filter icon opens the built-in column menu with an operator dropdown and a value input, and this example adds an active-filter chip strip above the grid, built with api.setFilter and api.clearFilter, so a filter can be removed with one click without opening the menu.
The grid's built-in column menu exposes a per-column operator + value filter - clicking the filter icon on a header opens it. This demo turns that surface on and adds an "active filter chips" strip on top, driven by the imperative API.
Imports, features and API used
Imports: @svgrid/grid, ../shared/seed
Table features registered: rowSortingFeature, columnFilteringFeature
Columns: firstName (First name), lastName (Last name), department (Department), country (Country), status (Status), age (Age), salary (Salary)
SvGridApi methods called: api.clearFilter(), api.setFilter()
Frequently asked questions
How do I turn on the column filter menu?
Register columnFilteringFeature and set filterMode='menu'. Each header gets a filter icon that opens the operator and value editor for that column's type.
How do I show active filters as chips?
Keep the filters in your own state through the API: api.setFilter(columnId, filter) applies one and api.clearFilter(columnId) removes it, and the chip strip renders from that state.
Which operators are available?
Text columns get contains, not contains, equals, not equals, starts with, ends with, regex, in, not in, blank and not blank. Number columns get equals, not equals, greater than, less than, between, in, not in, blank and not blank; date columns use the same before-and-after set with between. The menu shows the set that fits the column's editorType.
Related documentation
Related articles
- Excel-Style Filtering for Your Svelte Data Grid - Per-column filter menus, a filter row, global search, and server-side filtering - all wired through a single columnFilteringFeature in SvGrid.
- How We Built Excel-Style Filters - How SvGrid implements per-column filter menus, type-aware operators, and a shared filter model that works identically for in-memory and server-side data.
- Set Filters and Custom Filter Logic in SvGrid - Build Excel-style checkbox set filters with live facet counts and a custom numeric range filter - using four API calls and plain Svelte markup.
Source code (03-excel-filters.svelte)
<script lang="ts">
/**
* 03. Excel-style filters
* -----------------------
* The grid's built-in column menu exposes a per-column operator + value
* filter - clicking the filter icon on a header opens it. This demo turns
* that surface on and adds an "active filter chips" strip on top, driven
* by the imperative API.
*/
import {
SvGrid,
tableFeatures,
rowSortingFeature,
columnFilteringFeature,
type GridColumns,
type SvGridApi,
type SvGridFilterOperator,
} from '@svgrid/grid'
import { makePeople, type Person } from '../shared/seed'
const features = tableFeatures({
rowSortingFeature,
columnFilteringFeature,
})
const rows = makePeople(2_500)
const columns: GridColumns<Person> = [
{ field: 'firstName', header: 'First name', editorType: 'text' },
{ field: 'lastName', header: 'Last name', editorType: 'text' },
{ field: 'department', header: 'Department', editorType: 'text' },
{ field: 'country', header: 'Country', editorType: 'text' },
{ field: 'status', header: 'Status', editorType: 'text' },
{ field: 'age', header: 'Age', editorType: 'number' },
{
field: 'salary',
header: 'Salary',
editorType: 'number',
format: { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } },
},
]
type ActiveChip = { columnId: string; operator: SvGridFilterOperator; value?: string }
let api = $state<SvGridApi<typeof features, Person> | null>(null)
let chips = $state<ActiveChip[]>([])
function applyChip(chip: ActiveChip) {
chips = [...chips.filter((c) => c.columnId !== chip.columnId), chip]
api?.setFilter(chip.columnId, { operator: chip.operator, value: chip.value })
}
function removeChip(columnId: string) {
chips = chips.filter((c) => c.columnId !== columnId)
api?.clearFilter(columnId)
}
function clearAll() {
for (const chip of chips) api?.clearFilter(chip.columnId)
chips = []
}
// Quick presets to demonstrate the API surface.
function preset(name: 'engineers' | 'senior' | 'active') {
clearAll()
if (name === 'engineers') applyChip({ columnId: 'department', operator: 'equals', value: 'Engineering' })
if (name === 'senior') applyChip({ columnId: 'age', operator: 'greaterThan', value: '50' })
if (name === 'active') applyChip({ columnId: 'status', operator: 'equals', value: 'active' })
}
</script>
<section class="flex flex-col flex-1 min-h-0 gap-3">
<div class="flex flex-wrap items-center gap-2 text-sm shrink-0">
<span class="font-medium">Quick presets:</span>
<button onclick={() => preset('engineers')} class="xf-btn rounded px-3 py-1">Engineering only</button>
<button onclick={() => preset('senior')} class="xf-btn rounded px-3 py-1">Age > 50</button>
<button onclick={() => preset('active')} class="xf-btn rounded px-3 py-1">Active only</button>
<span class="xf-hint ml-3">Or click the filter icon on any column header.</span>
</div>
{#if chips.length}
<div class="flex flex-wrap items-center gap-2 text-xs shrink-0">
<span class="xf-hint">Active:</span>
{#each chips as chip (chip.columnId)}
<span class="xf-chip inline-flex items-center gap-1 rounded-full px-2 py-0.5">
{chip.columnId} {chip.operator} {chip.value ?? ''}
<button onclick={() => removeChip(chip.columnId)} aria-label="Remove filter" class="xf-chip-x rounded-full px-1">×</button>
</span>
{/each}
<button onclick={clearAll} class="xf-clear ml-2 underline">Clear all</button>
</div>
{/if}
<div class="flex-1 min-h-0">
<SvGrid responsive={true}
columnResize
data={rows}
columns={columns}
features={features}
filterMode="menu"
selectionMode="cell"
enableInlineEditing={false}
enableCellSelection={true}
rowHeight={36}
containerHeight="100%"
fitColumns={true}
onApiReady={(next) => (api = next)}
/>
</div>
</section>
<style>
.xf-btn {
border: 1px solid var(--sg-border, #cbd5e1);
background: var(--sg-bg, #ffffff);
color: var(--sg-fg, #0f172a);
}
.xf-btn:hover { background: var(--sg-row-hover-bg, #f1f5f9); }
.xf-hint { color: var(--sg-muted, #64748b); }
.xf-clear { color: var(--sg-muted, #475569); }
.xf-chip {
background: color-mix(in srgb, var(--sg-accent, #2563eb) 14%, transparent);
color: var(--sg-accent, #1d4ed8);
}
.xf-chip-x:hover { background: color-mix(in srgb, var(--sg-accent, #2563eb) 26%, transparent); }
</style>More Filtering & Search examples
- Sort, filter, paginate - Three most-asked-for features wired together against ~5k rows.
- Filter - between operator - Number + date columns expose a "Between" operator with From/To inputs. Drive it via the menu or imperatively with api.setFilter(id, { operator: "between", value, valueTo }).
- Highlighted search matches - External search input + a custom cell snippet that wraps matched substrings in <mark>. Filters the dataset AND visually flags hits.
- Find in grid (Ctrl+F) - Built-in find overlay with next / previous navigation. Scans every visible cell value; matches activate + scroll into view.
- Locale-aware text filter - Pass filterLocale and the grid normalises text (NFD + diacritic strip + locale-aware lowercase) so "cafe" matches "Café", "tokyo" matches "Tōkyō".