Skip to content

Filter Header

FilterHeaderPlugin places common filter controls directly below each column label. Use it when users need fast, always-visible filtering; the normal filter icon remains available for the complete advanced-filter popup.

Source code
TypeScriptts
// src/components/filter-header/FilterAdvanced.ts

import { defineCustomElements } from '@revolist/revogrid/loader';
import {
  AdvanceFilterPlugin,
  ColumnStretchPlugin,
  FilterHeaderPlugin,
  RowOddPlugin,
  RowSelectPlugin,
  SameValueMergePlugin,
} from '@revolist/revogrid-pro';
defineCustomElements();

import { currentTheme } from '../composables/useRandomData';
import { makeData } from '../composables/makeData';

const { isDark } = currentTheme();
export function load(parentSelector: string, rows = makeData(100)) {
  const grid = document.createElement('revo-grid');

  grid.columns = [
    {
      name: 'Superheroes',
      children: [
        {
          name: 'First Name',
          prop: 'firstName',
          filter: true,
          sortable: true,
          rowSelect: true,
          columnTemplate: (h) =>
            h(
              'span',
              { style: { fontWeight: 'bold', color: '#0066cc' } },
              'Custom First Name',
            ),
        },
        {
          name: 'Last Name',
          prop: 'lastName',
          filter: ['selection'],
          sortable: true,
        },
      ],
    },
    {
      name: 'Params',
      children: [
        {
          name: 'Gender',
          prop: 'gender',
          filter: ['selection'],
          sortable: true,
        },
        {
          name: 'Age',
          prop: 'age',
          filter: ['number', 'slider'],
          sortable: true,
        },
      ],
    },
  ];
  grid.plugins = [
    AdvanceFilterPlugin,
    FilterHeaderPlugin,
    ColumnStretchPlugin,
    SameValueMergePlugin,
    RowSelectPlugin,
    RowOddPlugin,
  ];
  grid.stretch = 'all';
  grid.filter = {
    localization: {
      captions: {
        selectionAll: 'Any value',
      },
    },
    slider: {
      showRangeInputs: true,
    },
  };
  grid.theme = isDark() ? 'darkMaterial' : 'material';
  grid.hideAttribution = true;
  grid.resize = true;
  document.querySelector(parentSelector)?.appendChild(grid);
  grid.source = rows;
  return () => grid.remove();
}
Vuevue
// src/components/filter-header/FilterAdvanced.vue

<template>
  <VGrid
    class="grow h-full cell-border"
    :theme="isDark ? 'darkMaterial' : 'material'"
    :columns="columns"
    :source="rows"
    :plugins="plugins"
    stretch="all"
    :filter="filter"
    style="min-height: 400px"
    hide-attribution
    resize
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { currentThemeVue } from '../composables/useRandomData';
import {
  type ColumnGrouping,
  type ColumnRegular,
  VGrid,
} from '@revolist/vue3-datagrid';
import {
  AdvanceFilterPlugin,
  FilterHeaderPlugin,
  ColumnStretchPlugin,
  SameValueMergePlugin,
  RowSelectPlugin,
  RowOddPlugin,
} from '@revolist/revogrid-pro';
import { makeData } from '../composables/makeData';

const { isDark } = currentThemeVue();

const columns = ref<(ColumnRegular | ColumnGrouping)[]>([
  {
    name: 'Superheroes',
    children: [
      {
        name: 'First Name',
        prop: 'firstName',
        filter: true,
        sortable: true,
        rowSelect: true,
        columnTemplate: (h) =>
          h(
            'span',
            { style: { fontWeight: 'bold', color: '#0066cc' } },
            'Custom First Name',
          ),
      },
      {
        name: 'Last Name',
        prop: 'lastName',
        filter: ['selection'],
        sortable: true,
      },
    ],
  },
  {
    name: 'Params',
    children: [
      {
        name: 'Gender',
        prop: 'gender',
        filter: ['selection'],
        sortable: true,
      },
      {
        name: 'Age',
        prop: 'age',
        filter: ['number', 'slider'],
        sortable: true,
      },
    ],
  },
]);

const plugins = [
  AdvanceFilterPlugin,
  FilterHeaderPlugin,
  ColumnStretchPlugin,
  SameValueMergePlugin,
  RowSelectPlugin,
  RowOddPlugin,
];

const rows = ref(makeData(100));

const filter = ref({
  localization: {
    captions: {
      selectionAll: 'Any value',
    },
  },
  slider: {
    showRangeInputs: true,
  },
});
</script>
Reacttsx
// src/components/filter-header/FilterAdvanced.tsx

import { useMemo, useState } from 'react';
import {
  RevoGrid,
  type ColumnGrouping,
  type ColumnRegular,
  type DataType,
} from '@revolist/react-datagrid';
import {
  AdvanceFilterPlugin,
  ColumnStretchPlugin,
  FilterHeaderPlugin,
  RowOddPlugin,
  RowSelectPlugin,
  SameValueMergePlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { makeData } from '../composables/makeData';

type FilterAdvancedProps = {
  rows?: DataType[];
};

function FilterAdvanced({ rows }: FilterAdvancedProps) {
  const { isDark } = currentTheme();
  const [source] = useState<DataType[]>(() => rows ?? makeData(100));
  const filter = useMemo(
    () => ({
      localization: {
        captions: {
          selectionAll: 'Any value',
        },
      },
      slider: {
        showRangeInputs: true,
      },
    }),
    [],
  );
  const plugins = useMemo(
    () => [
      AdvanceFilterPlugin,
      FilterHeaderPlugin,
      ColumnStretchPlugin,
      SameValueMergePlugin,
      RowSelectPlugin,
      RowOddPlugin,
    ],
    [],
  );

  const columns = useMemo<(ColumnRegular | ColumnGrouping)[]>(
    () => [
      {
        name: 'Superheroes',
        children: [
          {
            name: 'First Name',
            prop: 'firstName',
            filter: true,
            sortable: true,
            rowSelect: true,
            columnTemplate: (h) =>
              h(
                'span',
                { style: { fontWeight: 'bold', color: '#0066cc' } },
                'Custom First Name',
              ),
          },
          {
            name: 'Last Name',
            prop: 'lastName',
            filter: ['selection'],
            sortable: true,
          },
        ],
      },
      {
        name: 'Params',
        children: [
          {
            name: 'Gender',
            prop: 'gender',
            filter: ['selection'],
            sortable: true,
          },
          {
            name: 'Age',
            prop: 'age',
            filter: ['number', 'slider'],
            sortable: true,
          },
        ],
      },
    ],
    [],
  );

  return (
    <RevoGrid
      plugins={plugins}
      className="grow h-full cell-border"
      columns={columns}
      source={source}
      stretch="all"
      filter={filter}
      hide-attribution
      theme={isDark() ? 'darkMaterial' : 'material'}
      style={{ minHeight: 400 }}
      resize
    />
  );
}

export default FilterAdvanced;
Angularts
// src/components/filter-header/FilterAdvancedAngular.ts

import { Component, ViewEncapsulation } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import {
  AdvanceFilterPlugin,
  ColumnStretchPlugin,
  FilterHeaderPlugin,
  RowOddPlugin,
  RowSelectPlugin,
  SameValueMergePlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { makeData } from '../composables/makeData';

@Component({
  selector: 'filter-header-grid',
  standalone: true,
  imports: [RevoGrid],
  template: ` <revo-grid
    [plugins]="plugins"
    class="grow h-full cell-border"
    [columns]="columns"
    [source]="source"
    stretch="all"
    [filter]="filter"
    [hideAttribution]="true"
    [theme]="theme"
    [resize]="true"
    style="min-height: 400px;"
  ></revo-grid>`,
  encapsulation: ViewEncapsulation.None,
})
export class FilterHeaderGridComponent {
  theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
  source = makeData(100);

  columns = [
    {
      name: 'Superheroes',
      children: [
        {
          name: 'First Name',
          prop: 'firstName',
          filter: true,
          sortable: true,
          rowSelect: true,
          columnTemplate: (h) =>
            h(
              'span',
              { style: { fontWeight: 'bold', color: '#0066cc' } },
              'Custom First Name',
            ),
        },
        {
          name: 'Last Name',
          prop: 'lastName',
          filter: ['selection'],
          sortable: true,
        },
      ],
    },
    {
      name: 'Params',
      children: [
        {
          name: 'Gender',
          prop: 'gender',
          filter: ['selection'],
          sortable: true,
        },
        {
          name: 'Age',
          prop: 'age',
          filter: ['number', 'slider'],
          sortable: true,
        },
      ],
    },
  ];

  filter = {
    localization: {
      captions: {
        selectionAll: 'Any value',
      },
    },
    slider: {
      showRangeInputs: true,
    },
  };
  plugins = [
    AdvanceFilterPlugin,
    FilterHeaderPlugin,
    ColumnStretchPlugin,
    SameValueMergePlugin,
    RowSelectPlugin,
    RowOddPlugin,
  ];
}

Register FilterHeaderPlugin, then configure filter families on the columns. It installs AdvanceFilterPlugin automatically:

import {
FilterHeaderPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [FilterHeaderPlugin];
grid.columns = [
{ name: 'Name', prop: 'name', filter: 'string' },
{ name: 'Status', prop: 'status', filter: ['selection'] },
{ name: 'Age', prop: 'age', filter: ['number', 'slider'] },
{ name: 'Tags', prop: 'tags', filter: ['array'] },
];

Text columns receive a debounced input. Popup-style and registered structured filters receive a compact trigger. Slider columns receive either an inline range control or a formatted range summary.

Column filter familyHeader control
Text and numberDebounced inline input
SelectionPopup trigger with included-value summary
DatePopup trigger with active-state indicator
BooleanPopup trigger for the valueless Yes/No operators
ArrayPopup trigger for empty/non-empty array operators
SliderInline range control or formatted range summary
Structured typesPopup trigger using the type’s readable summary

A column containing the slider filter type renders the shared range slider directly in the header. The regular filter icon remains available for opening the full advanced-filter popup.

const columns = [{ name: 'Age', prop: 'age', filter: ['number', 'slider'] }];

Moving either handle applies the range immediately. Returning both handles to the full data range clears the slider filter.

For selection, date, boolean, and array popup filters, the header renders a popup trigger instead of a free text input. Clicking it opens the same advanced-filter popup available from the normal filter icon.

Selection triggers show All while no values are excluded. Opening a selection popup and closing it without changes keeps the header value as All; it does not expand to every selected item.

Customize that fallback through the standard advanced-filter localization captions:

const filter = {
localization: {
captions: {
selectionAll: 'Any value',
},
},
};

When values are unchecked in the selection popup, the trigger shows a compact selected/total badge such as 8/9. This is the default presentation and requires no additional configuration. Hover the badge to see the full selected-value summary. By default, that summary includes the number of matching rows for each value; set hideFilterHeaderCount on the column to hide those row counts.

Date triggers become active when a date condition has a value. Boolean and array triggers become active when one of their valueless operators is applied. Array support requires the array family in the column’s filter array:

grid.columns = [{ name: 'Tags', prop: 'tags', filter: ['array'] }];

The array popup offers Is empty array and Is not empty array. See Array Filter for their strict array-value behavior.

Every popup-style filter uses the same FilterHeaderTemplateFunc contract. A column template has the highest priority:

import type { FilterHeaderTemplateFunc } from '@revolist/revogrid-pro';
const compactStatusHeader: FilterHeaderTemplateFunc = (h, { presentation }) => (
<span class={{ 'status-summary': true, active: presentation.active }}>
{presentation.summary}
</span>
);
const columns = [{
name: 'Status',
prop: 'status',
filter: ['selection'],
filterHeaderTemplate: compactStatusHeader,
}];

The grid owns the actual button, focus behavior, tooltip, keyboard activation, and accessible name. A template only renders non-interactive visual content inside that button. Use presentation.summary for compact text and presentation.details for the full human-readable meaning. Returning undefined keeps the standard text or count fallback.

A custom registered structured type may provide the same template once for every column using that type:

const temperatureFilter: StructuredFilterType = {
id: 'temperatureRange',
operatorIds: ['temperatureBetween'],
render: context => <TemperatureEditor {...context} />,
describeCondition: ({ condition }) => ({
summary: `${condition.value.min}${condition.value.max} °C`,
details: `Temperature from ${condition.value.min} to ${condition.value.max} °C`,
}),
headerControl: {
kind: 'popup',
template: (h, { presentation }) => (
<span>{presentation.summary}</span>
),
},
};

Use kind: 'popup' when the filter needs custom badge, star, or summary content inside the grid-owned trigger. Use a headerControl resolver with kind: 'inline' when the type needs an interactive control such as a range slider. The resolver receives the same controlled body context as the popup, including aggregates and replaceConditions(), while the grid owns the outer non-sorting interaction boundary.

describeCondition is the source of truth for filter badges, grouped-filter text, header labels, and accessibility text. Keep it data-independent: it receives the condition and column configuration, not grid rows. This makes compact presentation safe for remote and very large datasets. A column-level filterHeaderTemplate overrides a type-owned popup template.

The template props also expose the column, canonical conditions, selection compatibility values, and the complete presentation object. The former top-level active and text fields remain as compatibility aliases; new code should use presentation.

Localize the surrounding accessible text with the normal filter captions:

const filter = {
localization: {
captions: {
filterHeaderLabel: column => `Filter ${column.name ?? column.prop}`,
filterHeaderSelectionSummary: (selected, total, details) =>
`${selected} of ${total} selected${details ? `: ${details}` : ''}`,
},
},
};

Header controls prioritize quick changes and limited space. Keep the normal popup workflow for multiple conditions, expressions, Excel-style staged selection, grouped AST editing, or any control that needs explanatory content.