Filter Styling
Introduction
Section titled “Introduction”The per-column filter popup — the panel that opens from a column’s filter funnel —
is theme-aware in core. It reads the grid’s font, a coherent accent, radius and
shadow from a dedicated --zg-filter-* token group, so it matches your grid without
any configuration. Retheming it is as simple as overriding those tokens:
.zg-grid { --zg-filter-radius: 12px; --zg-filter-accent: #7c3aed; /* focus rings, primary button, segmented toggle */ --zg-filter-condition-bg: #faf5ff;}That covers the common case. When you need to go further — target a single part, add
your own classes, or replace a part’s DOM entirely — reach for filterTheme(), an
enterprise helper that drives the popup’s customization surface at runtime:
import { filterTheme } from '@zengrid/enterprise/grid';
const theme = filterTheme(grid, { tokens: { '--zg-filter-radius': '12px', '--zg-filter-accent': '#7c3aed' }, parts: { applyBtn: { class: 'my-apply' }, valueInput: { style: { fontWeight: '600' } } }, render: { title: (ctx) => myTitle(ctx.columnDef) },});// later: theme.update({ ... }) or theme.detach()filterTheme() is license-gated (it asserts the filter-theme feature). It’s the
ergonomic front-end to core’s filterUI.setCustomization extension point, so the deep
customization lives in the library, not in your app’s CSS overrides.
The three levers
Section titled “The three levers”tokens— CSS custom properties set on the popup root. The cleanest path: retint the whole popup by overriding--zg-filter-*.parts— per-partclassand inlinestyle, composed over the defaults. Parts:popup,header,title,close,match,conditions,conditionRow,operatorSelect,valueInput,valueInputTo,addBtn,actions,applyBtn,clearBtn.render— replace a part’s DOM outright. Return an element and it becomes that part; return nothing and the default is kept. Replacing a control part (operatorSelect/valueInput*) means your element becomes the control, so it must expose a.value.
Override --zg-filter-* to retint the whole popup. Open a column funnel, then edit the token values and re-open.
interface FilterPopupCustomization { tokens?: Record<string, string>; // set on the popup root parts?: Partial<Record<FilterPart, { class?: string | string[]; style?: Record<string, string>; }>>; render?: Partial<Record<FilterPart, (ctx: FilterPartContext) => HTMLElement | void>>;}
type FilterPart = | 'popup' | 'header' | 'title' | 'close' | 'match' | 'conditions' | 'conditionRow' | 'operatorSelect' | 'valueInput' | 'valueInputTo' | 'addBtn' | 'actions' | 'applyBtn' | 'clearBtn';
interface FilterPartContext { part: FilterPart; columnDef: ColumnDef; dataCol: number; defaultElement: HTMLElement; // what the popup built by default}
function filterTheme(grid, options?: FilterThemeOptions): FilterThemeHandle;
interface FilterThemeHandle { update(options: FilterThemeOptions): void; // replace the customization detach(): void; // restore the default popup}- Core is already theme-aware. You only need
filterTheme()for per-part or slot-level control — plain retinting is just--zg-filter-*in your stylesheet. filterTheme()applies to the next open. It re-opens cleanly; a popup that’s already open closes so the new customization takes effect.- Replacing control parts is powerful but fragile — your element becomes the
control and must expose a
.valuethe popup can read on Apply. - Tokens over
!important. Prefer token overrides and part classes to fighting the defaults with specificity.
- Filter Templates — save a filter and apply it in one click.
- Custom Column Filters — column-level filter configuration.
- Applying Filters — the
grid.filter.*API.