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 }).
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
The between operator of the Svelte 5 data grid's column filters. Number and date columns render From and To inputs in the column menu and keep rows whose value falls in the inclusive range; the same filter is set from code with api.setFilter('price', { operator: 'between', value: '100', valueTo: '500' }). api.getFilters() returns the same shape, so filter sets can be saved to localStorage or the URL, and onFiltersChange keeps a visible summary current.
The column-menu filter UI exposes a between operator for number and date columns. It renders two inputs (From / To); the engine keeps rows whose value is in [from, to] inclusive.
Drive it from the menu (three dots on a column header) or imperatively via:
api.setFilter('price', { operator: 'between', value: '100', valueTo: '500' })
api.getFilters() round-trips the same { operator, value, valueTo } shape so you can save filter sets to localStorage / URL params.
Text columns deliberately do not have a between operator (it's rarely what users mean for strings).
Imports, features and API used
Imports: @svgrid/grid, ../shared/seed
Table features registered: rowSortingFeature, columnFilteringFeature
Columns: orderId (Order ID), company (Company), product (Product), sellDate (Sell date), quantity (Qty), price (Price), country (Country)
SvGridApi methods called: api.getFilters(), api.setFilter()
Frequently asked questions
How do I filter a column to a range?
Open the column menu, pick Between and fill From and To. From code: api.setFilter(columnId, { operator: 'between', value, valueTo }). Both ends are inclusive.
Can I persist the filters?
Yes. api.getFilters() returns every active filter as { operator, value, valueTo } keyed by column, which serializes cleanly; feed it back through api.setFilter on load.
Why is there no Between for text columns?
A lexical range is rarely what users mean for strings, so text columns offer contains, equals, starts with, ends with, regex and the set operators instead.
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.
- Server-Side Data - Pagination, Sorting, and Filtering on the Backend - Keep 100,000+ rows on the server. SvGrid owns the UI state for sort, filter, and pagination controls - your API owns the data.
Source code (64-filter-between-operator.svelte)
<script lang="ts">
/**
* 64. Filter - `between` operator
* ------------------------------
* The column-menu filter UI exposes a `between` operator for number
* and date columns. It renders two inputs (`From` / `To`); the engine
* keeps rows whose value is in `[from, to]` inclusive.
*
* Drive it from the menu (three dots on a column header) or
* imperatively via:
*
* api.setFilter('price', { operator: 'between', value: '100', valueTo: '500' })
*
* `api.getFilters()` round-trips the same `{ operator, value, valueTo }`
* shape so you can save filter sets to localStorage / URL params.
*
* Text columns deliberately do not have a `between` operator (it's
* rarely what users mean for strings).
*/
import {
SvGrid,
tableFeatures,
rowSortingFeature,
columnFilteringFeature,
type GridColumns,
type SvGridApi,
} from '@svgrid/grid'
import { makeOrders, type Order } from '../shared/seed'
const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
const rows = makeOrders(500)
let api = $state<SvGridApi<typeof features, Order> | null>(null)
let activeFilters = $state<Record<string, { operator: string; value: string; valueTo?: string }>>({})
function filterMidRangePrices() {
if (!api) return
api.setFilter('price', { operator: 'between', value: '100', valueTo: '300' })
}
function filterLargeQty() {
if (!api) return
api.setFilter('quantity', { operator: 'between', value: '50', valueTo: '120' })
}
/**
* Date filter is correct against the seed: `makeOrders` generates dates
* over the past 365 days, so a [today-180, today] window always returns
* data. The engine compares ISO-8601 date strings lexicographically -
* works because YYYY-MM-DD orders chronologically.
*/
function filterRecentSixMonths() {
if (!api) return
const now = new Date()
const from = new Date(now.getTime() - 180 * 86_400_000)
api.setFilter('sellDate', {
operator: 'between',
value: from.toISOString().slice(0, 10),
valueTo: now .toISOString().slice(0, 10),
})
}
function filterOlderHalf() {
if (!api) return
const now = new Date()
const from = new Date(now.getTime() - 365 * 86_400_000)
const to = new Date(now.getTime() - 181 * 86_400_000)
api.setFilter('sellDate', {
operator: 'between',
value: from.toISOString().slice(0, 10),
valueTo: to .toISOString().slice(0, 10),
})
}
function clearAll() {
if (!api) return
for (const id of Object.keys(api.getFilters())) api.setFilter(id, null)
}
const columns: GridColumns<Order> = [
{ field: 'orderId', header: 'Order ID', editorType: 'text', width: 140 },
{ field: 'company', header: 'Company', editorType: 'text', width: 180 },
{ field: 'product', header: 'Product', editorType: 'text', width: 180 },
{ field: 'sellDate', header: 'Sell date',editorType: 'date', width: 130,
format: { type: 'date', pattern: 'y-m-d' } },
{ field: 'quantity', header: 'Qty', editorType: 'number', width: 90 },
{ field: 'price', header: 'Price', editorType: 'number', width: 130,
format: { type: 'currency', currency: 'USD' } },
{ field: 'country', header: 'Country', editorType: 'text', width: 110 },
]
</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 filters:</span>
<button type="button" onclick={filterMidRangePrices}
class="qf-btn rounded-md px-3 py-1.5"
>Price $100 - $300</button>
<button type="button" onclick={filterLargeQty}
class="qf-btn rounded-md px-3 py-1.5"
>Qty 50 - 120</button>
<button type="button" onclick={filterRecentSixMonths}
class="qf-btn rounded-md px-3 py-1.5"
>Sell date - last 6 months</button>
<button type="button" onclick={filterOlderHalf}
class="qf-btn rounded-md px-3 py-1.5"
>Sell date - older half (180 - 365 days ago)</button>
<button type="button" onclick={clearAll}
class="rounded-md border border-rose-300 dark:border-rose-700 px-3 py-1.5 text-rose-700 dark:text-rose-300 hover:bg-rose-50 dark:hover:bg-rose-950/50"
>Clear all</button>
<span class="qf-hint ml-auto text-xs">
Or open the column menu (three dots) and pick Between.
</span>
</div>
{#if Object.keys(activeFilters).length > 0}
<div class="qf-panel rounded-md px-3 py-2 shrink-0">
<div class="qf-panel-label text-[10px] uppercase tracking-wide mb-1">Active filters</div>
<pre class="qf-pre text-xs leading-relaxed m-0">{JSON.stringify(activeFilters, null, 2)}</pre>
</div>
{/if}
<div class="flex-1 min-h-0">
<SvGrid responsive={true}
columnResize
data={rows}
columns={columns}
features={features}
filterMode="menu"
selectionMode="cell"
showRowNumbers={true}
showPagination={true}
pageSize={25}
enableInlineEditing={false}
enableCellSelection={true}
rowHeight={36}
containerHeight="100%"
fitColumns={true}
onApiReady={(next) => (api = next)}
onFiltersChange={() => { if (api) activeFilters = api.getFilters() }}
/>
</div>
</section>
<style>
.qf-btn {
border: 1px solid var(--sg-border, #cbd5e1);
background: var(--sg-bg, #ffffff);
color: var(--sg-fg, #0f172a);
}
.qf-btn:hover { background: var(--sg-row-hover-bg, #f8fafc); }
.qf-hint { color: var(--sg-muted, #64748b); }
.qf-panel {
border: 1px solid var(--sg-border, #e2e8f0);
background: var(--sg-bg-subtle, var(--sg-header-bg, #f8fafc));
}
.qf-panel-label { color: var(--sg-muted, #64748b); }
.qf-pre { color: var(--sg-fg, #334155); }
</style>More Filtering & Search examples
- Sort, filter, paginate - Three most-asked-for features wired together against ~5k rows.
- Excel-style filters - Per-column operator dropdown with active-filter chips and clear.
- 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ō".