Sort, filter and search
Four ways to narrow and order the rows: sorting (no feature needed), the column filters, the quick search over every column, and find, which marks matches without hiding a row. The demo below has all four: click a title, type in the row of small fields under the titles, or in the search box above.
Shift+click a second title to sort by two columns; the funnel on a title opens its filter.
Sorting
Section titled “Sorting”Sorting needs no feature: a click on a header sorts, a second click reverses, Shift+click adds the column to the
sort; from the keyboard, Enter and Shift+Enter on a header do the same. sortable: false on a column turns it off;
the multiSort: false setting keeps one key. Text sorts follow the grid’s locale (Arabic letters, accents,
digits). Empty cells are last in both directions (nulls: 'first' in a sort item puts them first).
There is no sort key on a column. Sort from code with a command, or set the first sort with the
state feature (gridState({ initial }), which sends a single request to a server).
import { createGrid } from '@dzgrid/dom';import { EN } from '@dzgrid/dom/locales/en';
type River = { id: string; name: string; length: number };
const grid = createGrid<River>(document.getElementById('rivers')!, { rows: [{ id: 'r1', name: 'Silverrun', length: 412 }, { id: 'r2', name: 'Oxbow', length: 96 }], columns: [{ id: 'name', header: 'River' }, { id: 'length', header: 'Length (km)', type: 'number' }], getRowId: (r) => r.id, messages: EN,});
grid.kernel.dispatch({ type: 'sort.set', sort: [{ column: 'length', dir: 'desc' }] }, 'api');grid.kernel.dispatch({ type: 'sort.toggle', column: 'name', multi: true }, 'api');console.log(grid.kernel.query.sort);Column filters
Section titled “Column filters”filters() adds a filter button to each header and a filter popup: text, number, day, instant, yes/no, and a value
list (kind: 'set'). Import it from @dzgrid/dom/filters (every kind; the day and instant forms use a calendar,
which needs @tzslot/*) or from @dzgrid/dom/filters-base (no calendar: text, number, yes/no and the value list
only — a day or instant column gets no filter, no button, and one missing-feature warning naming
@dzgrid/dom/filters; give it loadDates to bring the date forms back). Texts: -filters.
import { createGrid, type GridMessages } from '@dzgrid/dom';import type { ColumnOrGroup } from '@dzgrid/core';import { filters } from '@dzgrid/dom/filters';import { EN } from '@dzgrid/dom/locales/en';import { EN_FILTERS } from '@dzgrid/dom/locales/en-filters';
type Recipe = { id: string; name: string; cuisine: string; minutes: number; added: string; vegan: boolean };
const columns: ColumnOrGroup<Recipe>[] = [ { id: 'name', header: 'Recipe', filter: { kind: 'text', debounceMs: 200 } }, { id: 'cuisine', header: 'Cuisine', filter: { kind: 'set' } }, { id: 'minutes', header: 'Minutes', type: 'number', filter: { maxConditions: 2 } }, { id: 'added', header: 'Added', type: 'day' }, { id: 'vegan', header: 'Vegan', type: 'boolean' },];const messages: GridMessages = { ...EN, text: { ...EN.text, ...EN_FILTERS.text } };
const grid = createGrid<Recipe>(document.getElementById('recipes')!, { rows: [{ id: 'c1', name: 'Moon soup', cuisine: 'Lunar', minutes: 25, added: '2026-01-12', vegan: true }], columns, getRowId: (r) => r.id, messages, features: [filters({ floating: true, apply: 'live' })],});
grid.kernel.dispatch({ type: 'filter.column', column: 'minutes', filter: { type: 'number', op: 'lte', value: 30 } }, 'api');grid.kernel.dispatch({ type: 'filter.column', column: 'cuisine', filter: { type: 'set', include: ['Lunar'] } }, 'api');grid.kernel.dispatch({ type: 'filter.set', filter: {} }, 'api'); // clears every filterimport { Component, signal } from '@angular/core';import { DzGrid, type DzgColumnOrGroup } from '@dzgrid/angular';import type { ColumnFilter } from '@dzgrid/core';import { filters } from '@dzgrid/dom/filters';import { EN } from '@dzgrid/dom/locales/en';import { EN_FILTERS } from '@dzgrid/dom/locales/en-filters';
type Recipe = { id: string; name: string; cuisine: string; minutes: number; added: string; vegan: boolean };
@Component({ selector: 'app-recipes', imports: [DzGrid], template: ` <button type="button" (click)="quick()">Quick ones</button> <dz-grid [rows]="rows" [columns]="columns" [getRowId]="rowId" [messages]="messages" [features]="features" [(filter)]="filter" />`,})export class Recipes { readonly rows: Recipe[] = [{ id: 'c1', name: 'Moon soup', cuisine: 'Lunar', minutes: 25, added: '2026-01-12', vegan: true }]; readonly columns: DzgColumnOrGroup<Recipe>[] = [ { id: 'name', header: 'Recipe', filter: { kind: 'text', debounceMs: 200 } }, { id: 'cuisine', header: 'Cuisine', filter: { kind: 'set' } }, { id: 'minutes', header: 'Minutes', type: 'number', filter: { maxConditions: 2 } }, { id: 'added', header: 'Added', type: 'day' }, { id: 'vegan', header: 'Vegan', type: 'boolean' }, ]; readonly rowId = (r: Recipe) => r.id; readonly messages = { ...EN, text: { ...EN.text, ...EN_FILTERS.text } }; readonly features = [filters({ floating: true, apply: 'live' })]; // The filters, both ways: the user's changes come back here, a value set here is applied. readonly filter = signal<Readonly<Record<string, ColumnFilter>> | undefined>(undefined);
quick() { this.filter.set({ minutes: { type: 'number', op: 'lte', value: 30 } }); }}import '@dzgrid/element/define';import type { DzGridElement } from '@dzgrid/element';import { filters } from '@dzgrid/dom/filters';import { EN } from '@dzgrid/dom/locales/en';import { EN_FILTERS } from '@dzgrid/dom/locales/en-filters';
type Recipe = { id: string; name: string; cuisine: string; minutes: number; added: string; vegan: boolean };
const grid = document.querySelector<DzGridElement<Recipe>>('dz-grid')!;grid.columns = [ { id: 'name', header: 'Recipe', filter: { kind: 'text', debounceMs: 200 } }, { id: 'cuisine', header: 'Cuisine', filter: { kind: 'set' } }, { id: 'minutes', header: 'Minutes', type: 'number', filter: { maxConditions: 2 } }, { id: 'added', header: 'Added', type: 'day' }, { id: 'vegan', header: 'Vegan', type: 'boolean' },];grid.getRowId = (r) => r.id;grid.messages = { ...EN, text: { ...EN.text, ...EN_FILTERS.text } };grid.features = [filters({ floating: true, apply: 'live' })];grid.rows = [{ id: 'c1', name: 'Moon soup', cuisine: 'Lunar', minutes: 25, added: '2026-01-12', vegan: true }];
await grid.ready;grid.grid?.kernel.dispatch({ type: 'filter.column', column: 'minutes', filter: { type: 'number', op: 'lte', value: 30 } }, 'api');floating: trueadds a row of small filter fields under the headers;apply: 'button'waits for the Apply button instead of filtering as you type.rowHeight: 44gives that row its own height (px at 16 px text, 24 to 1 000); the row height otherwise.filter: falseon a column removes its filter;filter.opslimits the operators offered.- A filter on a
dayorinstantcolumn is resolved in the grid’stimeZone, half-open, to the millisecond — also when it is sent to a server. - The filter shapes (
ColumnFilter):text,number,day,instant,boolean,set,combined(and/orof conditions),custom(your own kind, registered with thefilterKindssetting).
Every option of filters() is on its page.
The filters panel
Section titled “The filters panel”filtersPanel() from @dzgrid/dom/filters-panel lists every column’s filter in a side panel (texts
-filters-panel, CSS filters-panel.css). Inside a dock it becomes one of the dock’s panels.
Quick search
Section titled “Quick search”quickSearch() from @dzgrid/dom/search filters on every visible column at once, from an input you own; matches are
highlighted (CSS search.css).
import { createGrid } from '@dzgrid/dom';import { quickSearch } from '@dzgrid/dom/search';import { EN } from '@dzgrid/dom/locales/en';import '@dzgrid/theme/search.css';
type Cloud = { id: string; name: string; height: number };
const search = quickSearch({ highlight: true });createGrid<Cloud>(document.getElementById('clouds')!, { rows: [{ id: 'n1', name: 'Cirrus feather', height: 9000 }], columns: [{ id: 'name', header: 'Cloud' }, { id: 'height', header: 'Height (m)', type: 'number' }], getRowId: (c) => c.id, messages: EN, features: [search],});search.bind(document.querySelector<HTMLInputElement>('#cloud-search')!, { debounceMs: 150 });import { Component, signal } from '@angular/core';import { DzGrid, DzgSearch, type DzgColumnOrGroup } from '@dzgrid/angular';import { quickSearch } from '@dzgrid/dom/search';import { EN } from '@dzgrid/dom/locales/en';
type Cloud = { id: string; name: string; height: number };
@Component({ selector: 'app-clouds', imports: [DzGrid, DzgSearch], template: ` <input type="search" aria-label="Search the clouds" [dzgSearch]="search" /> <dz-grid [rows]="rows" [columns]="columns" [getRowId]="rowId" [messages]="messages" [features]="features" [(quick)]="text" /> <p>Searching: {{ text() ?? '' }}</p>`,})export class Clouds { readonly rows: Cloud[] = [{ id: 'n1', name: 'Cirrus feather', height: 9000 }]; readonly columns: DzgColumnOrGroup<Cloud>[] = [{ id: 'name', header: 'Cloud' }, { id: 'height', header: 'Height (m)', type: 'number' }]; readonly rowId = (c: Cloud) => c.id; readonly messages = EN; readonly search = quickSearch({ highlight: true }); readonly features = [this.search]; readonly text = signal<string | undefined>(undefined);}import '@dzgrid/element/define';import type { DzGridElement } from '@dzgrid/element';import { quickSearch } from '@dzgrid/dom/search';import { EN } from '@dzgrid/dom/locales/en';
type Cloud = { id: string; name: string; height: number };
const search = quickSearch({ highlight: true });const grid = document.querySelector<DzGridElement<Cloud>>('dz-grid')!;grid.columns = [{ id: 'name', header: 'Cloud' }, { id: 'height', header: 'Height (m)', type: 'number' }];grid.getRowId = (c) => c.id;grid.messages = EN;grid.features = [search];grid.rows = [{ id: 'n1', name: 'Cirrus feather', height: 9000 }];search.bind(document.querySelector<HTMLInputElement>('#cloud-search')!, { debounceMs: 150 });Searching follows the language: Arabic letters with or without hamza and short vowels, Arabic-Indic digits.
Find (Ctrl+F)
Section titled “Find (Ctrl+F)”find() from @dzgrid/dom/find replaces the browser’s Ctrl+F inside the grid: it marks a text in the cells of every
row the filters keep (rows inside closed tree nodes too) and walks the focus from hit to hit with Enter and
Shift+Enter, without hiding any row (texts -find, CSS find.css).
Click a cell, press Ctrl+F (Cmd+F on a Mac) and type « heron »: Enter goes to the next one.