Skip to content

Sticky Cells

Sticky Cells keep important checkpoint cells visible while users scroll through a virtualized grid. Mark cells with cellProperties; when the row crosses the top visibility edge, the marked cells render in an extra header row until the next sticky row replaces them.

Source code
import { defineCustomElements } from '@revolist/revogrid/loader';
import type { CellTemplateProp, ColumnGrouping, ColumnRegular } from '@revolist/revogrid';
import {
AdvanceFilterPlugin,
ColumnStretchPlugin,
FilterHeaderPlugin,
StickyCellsPlugin,
} from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
defineCustomElements();
const { isDark } = currentTheme();
const stickyCellProperties = (props: CellTemplateProp) => {
if (props.rowIndex % 10 !== 0) {
return undefined;
}
return {
'sticky-cell': true,
class: {
'sticky-source-cell': true,
},
};
};
const columns: (ColumnRegular | ColumnGrouping)[] = [
{
name: 'Pinned Identity',
children: [
{
name: 'Account',
prop: 'account',
pin: 'colPinStart',
size: 150,
filter: true,
cellProperties: stickyCellProperties,
},
],
},
{
name: 'Opportunity Workflow',
children: [
{
name: 'Stage',
prop: 'stage',
size: 130,
filter: ['selection'],
cellProperties: stickyCellProperties,
},
{
name: 'Status',
prop: 'status',
size: 140,
filter: ['selection'],
cellProperties: stickyCellProperties,
cellTemplate: (h, props) => h('span', { class: 'sticky-status' }, props.value),
},
{
name: 'Owner',
prop: 'owner',
size: 130,
filter: ['selection'],
},
{
name: 'Amount',
prop: 'amount',
size: 120,
filter: true,
},
],
},
{
name: 'Pinned Market',
children: [
{
name: 'Pinned End',
prop: 'region',
pin: 'colPinEnd',
size: 130,
filter: ['selection'],
cellProperties: stickyCellProperties,
},
],
},
];
const rows = Array.from({ length: 120 }, (_, index) => ({
account: `Account ${index}`,
stage: index % 10 === 0 ? `Milestone ${index / 10 + 1}` : `Stage ${(index % 4) + 1}`,
status: index % 10 === 0 ? 'Sticky checkpoint' : index % 3 === 0 ? 'Blocked' : 'In progress',
owner: ['Ada', 'Grace', 'Linus', 'Margaret'][index % 4],
amount: `$${(1250 + index * 175).toLocaleString()}`,
region: ['North', 'South', 'West'][index % 3],
}));
export function load(parentSelector: string) {
const parent = document.querySelector(parentSelector);
if (!parent) {
return;
}
const grid = document.createElement('revo-grid');
grid.className = 'sticky-cells-demo';
grid.style.height = '420px';
grid.theme = isDark() ? 'darkMaterial' : 'material';
grid.columns = columns;
grid.plugins = [AdvanceFilterPlugin, FilterHeaderPlugin, StickyCellsPlugin, ColumnStretchPlugin];
grid.additionalData = {
stretch: 'all',
};
grid.filter = {};
grid.hideAttribution = true;
parent.appendChild(grid);
grid.source = rows;
return () => grid.remove();
}

Add StickyCellsPlugin to the grid and return { 'sticky-cell': true } from a column cellProperties callback.

import { StickyCellsPlugin } from '@revolist/revogrid-pro';
const stickyCellProperties = (props) => {
return props.rowIndex % 10 === 0
? { 'sticky-cell': true }
: undefined;
};
const columns = [
{
name: 'Account',
prop: 'account',
cellProperties: stickyCellProperties,
},
{
name: 'Status',
prop: 'status',
cellProperties: stickyCellProperties,
},
];
grid.plugins = [StickyCellsPlugin];

If any cell in a row is marked as sticky, that row can become the active sticky row. The sticky header renders only the cells whose own cellProperties returned 'sticky-cell': true.

Sticky Cells render through the header layer, so the plugin can be combined with grouped headers and filter headers. When using the filter header plugin, register filtering before sticky cells so Sticky Cells wraps the final header content:

import {
AdvanceFilterPlugin,
FilterHeaderPlugin,
StickyCellsPlugin,
ColumnStretchPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [
AdvanceFilterPlugin,
FilterHeaderPlugin,
StickyCellsPlugin,
ColumnStretchPlugin,
];
grid.filter = {};
grid.additionalData = {
stretch: 'all',
};
  • One sticky row is active at a time.
  • A marked row becomes sticky only after the bottom edge of that source row scrolls above the viewport top.
  • Sticky cells work across colPinStart, rgCol, and colPinEnd.
  • Custom cellTemplate output is reused in the sticky header row.
  • The original marked cells are hidden only while their sticky overlay is active.