Skip to content

Row Spanning

Since v1.4.0Enterprise

Row spanning merges consecutive cells in a column that share the same value into a single tall cell — the vertical equivalent of a merged header. It turns a run of repeated values (a region, a category, an owner) into clean visual groups without changing your data. The cells below the anchor are simply not drawn, so there are no internal gridlines inside a group.

Row spanning ships in @zengrid/enterprise. Turn it on by passing the rowSpanning() provider to the grid’s spanProvider option, then give each column you want to merge a rowSpan resolver. For each anchor row the grid asks the resolver how many following rows the cell should span; those rows are absorbed into the tall anchor cell. Spanning is computed only for the rows in the render window, so it stays fast at a million rows — the resolver is never called once per hidden row.

Because a span merges a run of equal, consecutive values, sort your data by the spanned column first so the matching values sit next to each other.

interface ColumnDef {
field: string;
header: string;
width?: number;
/**
* Return how many rows this cell should span (1 = no span). Called once per
* anchor row, only for rows in the render window.
*/
rowSpan?: (params: RowSpanParams) => number;
}
interface RowSpanParams {
/** Value of the anchor cell — this column at `rowIndex`. */
value: unknown;
/** Absolute row index of the anchor cell. */
rowIndex: number;
/** Read this column's value at any absolute row, to look ahead. */
getValue: (row: number) => unknown;
}
import { rowSpanning } from '@zengrid/enterprise/grid';
const grid = new Zengrid(el, {
rowCount,
// Enable row spanning (Enterprise). Reads each column's rowSpan resolver.
spanProvider: rowSpanning(),
columns: [
{
field: 'region',
header: 'Region',
width: 160,
// Walk forward while the next row shares this value.
rowSpan: ({ value, rowIndex, getValue }) => {
let span = 1;
while (getValue(rowIndex + span) === value) span++;
return span;
},
},
{ field: 'name', header: 'Name', width: 240 },
{ field: 'revenue', header: 'Revenue', width: 160 },
],
});

Each tab spans a different way. Edit the rowSpan resolver — return a bigger or smaller count — and the grid re-merges. Row spanning follows display order, so these demos build their rows already grouped (columns line up with the data positionally: the first column is the first value in each row).

The Region column merges each run of equal values into one tall cell. Widen a group by returning a larger span, or delete rowSpan to see the ungrouped column.

  • Sort first. A span only merges a consecutive run of equal values. Group your data (sort by the spanned column) before rendering, or spans of 1 are all you’ll get.
  • Window-local by design. When a group starts above the visible area, the top visible row becomes the anchor for the rest of the run. This is what keeps spanning O(visible rows) at any scroll offset.
  • Other columns are untouched. Only columns with a rowSpan resolver merge; every other column still renders one cell per row.