Multi Row Headers
The Multi Row Header plugin renders banded, multi-level headers from the same nested ColumnGrouping structure RevoGrid already uses for grouped columns. Use it when shallow leaf columns should fill the unused header depth while deeper branches continue to show stacked group bands.
This is the header-focused feature to use for AG Grid or DevExtreme-style multi-level headers. It is separate from CellMergePlugin, which only merges body and pinned data cells.
Source code
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
ColumnCollapsePlugin,
ColumnStretchPlugin,
MultiRowHeaderPlugin,
RowOddPlugin,
} from '@revolist/revogrid-pro';
import {
multiRowHeaderColumns,
multiRowHeaderRows,
} from './multi-row-header.shared';
import { currentTheme } from '../composables/useRandomData';
import './MultiRowHeader.scss';
defineCustomElements();
const { isDark } = currentTheme();
export function load(parentSelector: string): () => void {
const parent = document.querySelector(parentSelector);
if (!parent) {
return () => undefined;
}
const wrapper = document.createElement('div');
const darkTheme = isDark();
wrapper.className = `multi-row-header-example flex grow flex-col${darkTheme ? ' multi-row-header-example--dark' : ''}`;
const grid = document.createElement('revo-grid');
grid.columns = multiRowHeaderColumns;
grid.plugins = [
MultiRowHeaderPlugin,
ColumnCollapsePlugin,
AdvanceFilterPlugin,
RowOddPlugin,
ColumnStretchPlugin,
];
grid.multiRowHeader = true;
grid.filter = true;
grid.stretch = 'last';
grid.theme = darkTheme ? 'darkMaterial' : 'material';
grid.hideAttribution = true;
wrapper.append(grid);
parent.append(wrapper);
grid.source = multiRowHeaderRows;
return () => {
wrapper.remove();
};
}
import type { DataType } from '@revolist/revogrid';
const bandClass = (tone: 'green' | 'blue' | 'muted') => () => ({
class: {
[`multi-row-header-band-${tone}`]: true,
},
});
export const multiRowHeaderColumns = [
{
name: 'Group A',
columnProperties: bandClass('muted'),
children: [
{ name: 'Athlete', prop: 'athlete', size: 180, filter: true },
{
name: 'Group B',
collapsible: true,
columnProperties: bandClass('muted'),
children: [
{ name: 'Country', prop: 'country', size: 160, filter: true },
{
name: 'Group C',
columnProperties: bandClass('green'),
children: [
{ name: 'Sport', prop: 'sport', size: 145, filter: true },
{
name: 'Group D',
columnProperties: bandClass('green'),
children: [
{ name: 'Total', prop: 'total', size: 140, filter: true },
{
name: 'Group E',
collapsible: true,
columnProperties: bandClass('green'),
children: [
{ name: 'Gold', prop: 'gold', size: 130, filter: true },
{
name: 'Group F',
collapsible: true,
columnProperties: bandClass('green'),
children: [
{ name: 'Silver', prop: 'silver', size: 130, filter: true },
{
name: 'Group G',
columnProperties: bandClass('blue'),
children: [
{ name: 'Bronze', prop: 'bronze', size: 140, filter: true },
],
},
{ name: 'Rank', prop: 'rank', filter: true },
],
},
],
},
],
},
],
},
],
},
],
},
{
name: 'Pinned Totals',
columnProperties: bandClass('muted'),
children: [
{ name: 'Score', prop: 'score', pin: 'colPinEnd', size: 112, filter: true },
],
},
];
export const balancedMultiRowHeaderColumns = [
{
name: 'Balanced',
children: [
{
name: 'Flat Leaf',
prop: 'flat',
size: 160,
suppressSpanHeaderHeight: true,
filter: true,
},
{
name: 'Nested',
children: [
{ name: 'Nested Leaf', prop: 'nested', size: 160, filter: true },
],
},
],
},
];
export const multiRowHeaderRows: DataType[] = [
{
athlete: 'Ada Stone',
country: 'USA',
sport: 'Cycling',
total: 12,
gold: 5,
silver: 4,
bronze: 3,
rank: 1,
score: 982,
flat: 'Balanced A',
nested: 'Nested A',
},
{
athlete: 'Noah Smith',
country: 'Canada',
sport: 'Rowing',
total: 9,
gold: 3,
silver: 4,
bronze: 2,
rank: 2,
score: 913,
flat: 'Balanced B',
nested: 'Nested B',
},
{
athlete: 'Mila Hart',
country: 'France',
sport: 'Fencing',
total: 8,
gold: 2,
silver: 3,
bronze: 3,
rank: 3,
score: 884,
flat: 'Balanced C',
nested: 'Nested C',
},
{
athlete: 'Leo King',
country: 'Japan',
sport: 'Judo',
total: 7,
gold: 2,
silver: 2,
bronze: 3,
rank: 4,
score: 851,
flat: 'Balanced D',
nested: 'Nested D',
},
];
import { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import {
AdvanceFilterPlugin,
ColumnCollapsePlugin,
ColumnStretchPlugin,
MultiRowHeaderPlugin,
RowOddPlugin,
} from '@revolist/revogrid-pro';
import {
multiRowHeaderColumns,
multiRowHeaderRows,
} from './multi-row-header.shared';
import { currentTheme } from '../composables/useRandomData';
import './MultiRowHeader.scss';
const { isDark } = currentTheme();
export default function MultiRowHeader() {
const darkTheme = isDark();
const plugins = useMemo(
() => [
MultiRowHeaderPlugin,
ColumnCollapsePlugin,
AdvanceFilterPlugin,
RowOddPlugin,
ColumnStretchPlugin,
],
[],
);
const columns = useMemo(() => multiRowHeaderColumns, []);
const rows = useMemo(() => multiRowHeaderRows, []);
return (
<div className={`multi-row-header-example flex grow flex-col${darkTheme ? ' multi-row-header-example--dark' : ''}`}>
<RevoGrid
className="grow"
columns={columns}
source={rows}
plugins={plugins}
multiRowHeader={true}
filter={true}
stretch="all"
theme={darkTheme ? 'darkMaterial' : 'material'}
hide-attribution
/>
</div>
);
}
<template>
<div
class="multi-row-header-example grow flex flex-col"
:class="{ 'multi-row-header-example--dark': isDark }"
>
<RevoGrid
class="grow"
:columns="columns"
:source="rows"
:plugins="plugins"
:multi-row-header.prop="true"
:filter="true"
stretch="all"
:theme="isDark ? 'darkMaterial' : 'material'"
hide-attribution
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import {
AdvanceFilterPlugin,
ColumnCollapsePlugin,
ColumnStretchPlugin,
MultiRowHeaderPlugin,
RowOddPlugin,
} from '@revolist/revogrid-pro';
import {
multiRowHeaderColumns,
multiRowHeaderRows,
} from './multi-row-header.shared';
import { currentThemeVue } from '../composables/useRandomData';
import './MultiRowHeader.scss';
const { isDark } = currentThemeVue();
const columns = ref(multiRowHeaderColumns);
const rows = ref(multiRowHeaderRows);
const plugins = [
MultiRowHeaderPlugin,
ColumnCollapsePlugin,
AdvanceFilterPlugin,
RowOddPlugin,
ColumnStretchPlugin,
];
</script>
import {
Component,
NO_ERRORS_SCHEMA,
ViewEncapsulation,
} from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import {
AdvanceFilterPlugin,
ColumnCollapsePlugin,
ColumnStretchPlugin,
MultiRowHeaderPlugin,
RowOddPlugin,
} from '@revolist/revogrid-pro';
import {
multiRowHeaderColumns,
multiRowHeaderRows,
} from './multi-row-header.shared';
import { currentTheme } from '../composables/useRandomData';
@Component({
selector: 'multi-row-header-grid',
standalone: true,
imports: [RevoGrid],
template: `
<div
class="multi-row-header-example"
[class.multi-row-header-example--dark]="theme === 'darkMaterial'"
>
<revo-grid
[columns]="columns"
[source]="source"
[plugins]="plugins"
[multiRowHeader]="true"
[filter]="true"
stretch="all"
[theme]="theme"
[hideAttribution]="true"
></revo-grid>
</div>
`,
styleUrls: ['./MultiRowHeader.scss'],
encapsulation: ViewEncapsulation.None,
schemas: [NO_ERRORS_SCHEMA],
})
export class MultiRowHeaderGridComponent {
theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
columns = multiRowHeaderColumns;
source = multiRowHeaderRows;
plugins = [
MultiRowHeaderPlugin,
ColumnCollapsePlugin,
AdvanceFilterPlugin,
RowOddPlugin,
ColumnStretchPlugin,
];
}
import { MultiRowHeaderPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');grid.plugins = [MultiRowHeaderPlugin];grid.multiRowHeader = true;Nested Columns
Section titled “Nested Columns”Define headers with the existing children column-group structure. Leaf columns still own data binding, sorting, filtering, editing, templates, and resizing.
grid.columns = [ { name: 'Group A', children: [ { name: 'Athlete 1', prop: 'athlete' }, { name: 'Group B', children: [ { name: 'Country 1', prop: 'country' }, { name: 'Group C', children: [ { name: 'Sport 1', prop: 'sport' }, ], }, ], }, ], },];Header Spanning
Section titled “Header Spanning”By default, shallow leaf headers span unused group rows. This matches the common spreadsheet behavior where a leaf header under a shallower branch fills the remaining vertical header space.
Disable spanning for a single column when you want balanced empty group space above the leaf:
grid.columns = [ { name: 'Balanced', children: [ { name: 'Flat Leaf', prop: 'flat', suppressSpanHeaderHeight: true, }, { name: 'Nested', children: [{ name: 'Nested Leaf', prop: 'nested' }], }, ], },];You can also disable spanning for the whole plugin:
grid.multiRowHeader = { spanLeafHeaders: false,};Configuration
Section titled “Configuration”grid.multiRowHeader = { spanLeafHeaders: true,};| Option | Default | Description |
|---|---|---|
spanLeafHeaders | true | Lets shallow leaf headers fill unused group depth. |
Compatibility
Section titled “Compatibility”- Works with nested
ColumnGroupingdefinitions. - Leaf headers keep sorting, filtering, custom templates, and resize behavior.
- Group headers continue through RevoGrid’s normal group header render hooks.
ColumnCollapsePlugincan be registered withMultiRowHeaderPluginto add expand/collapse controls to groups.- Pinned columns can split a group into pinned and unpinned visible bands, matching the behavior used by major data grids.