Column & row spanning (merged cells)
Spanning lets a single body cell cover multiple columns and/or rows -
merged report headers, grouped labels, financial statements. SvGrid does this
with a real colspan / rowspan merge engine; there are two ways to drive it.
Open the live example: Cell merging (spreadsheet shell) (Spreadsheet)
1. Explicit merges (spreadsheet-style)
Declare exact merges as MergeSpec[] and apply them with the
spreadsheetLayout action. The origin cell (rowIndex, columnId) spans
colspan columns right and rowspan rows down; covered cells are hidden.
<script lang="ts">
import { spreadsheetLayout, type MergeSpec } from '@svgrid/grid'
const merges: MergeSpec[] = [
{ rowIndex: 0, columnId: 'A', colspan: 6 }, // title bar
{ rowIndex: 14, columnId: 'A', colspan: 3 }, // "Total" label
]
</script>
<div use:spreadsheetLayout={{ merges, columnOrder: columns.map((c) => c.id) }}>
<SvGrid {data} {columns} />
</div>
See demos/170-cell-merging.svelte.
2. Declarative colSpan / rowSpan (value-driven)
For data-driven spanning - "merge each run of equal values", "this cell spans 2
columns when X" - put colSpan / rowSpan callbacks on the column and turn
them into merges with spansToMerges. This runs on the same merge engine
as option 1 (no separate code path).
<script lang="ts">
// `SpanColumn` rather than `ColumnDef`: `spansToMerges` needs `id` to be
// present, and on a ColumnDef it is optional.
import { spreadsheetLayout, spansToMerges, type SpanColumn } from '@svgrid/grid'
const columns: SpanColumn<Row>[] = [
{ id: 'region', field: 'region',
// merge each vertical run of equal regions
rowSpan: ({ data, rowIndex }) => {
if (rowIndex > 0 && rows[rowIndex - 1].region === data.region) return 1 // covered
let n = 1
while (rows[rowIndex + n]?.region === data.region) n += 1
return n
} },
{ id: 'country', field: 'country' },
{ id: 'amount', field: 'amount' },
]
// Recompute after sort / filter - indexes are display-row indexes.
const merges = $derived(spansToMerges(rows, columns))
</script>
<div use:spreadsheetLayout={{ merges, columnOrder: columns.map((c) => c.id) }}>
<SvGrid {data} {columns} />
</div>
colSpan / rowSpan receive CellSpanParams ({ data, rowIndex, columnId, value }) and return the span count (1 = no span). spansToMerges handles
covered-cell bookkeeping so overlapping spans never double-emit.
Virtualization note
rowSpan uses real rowspan, which needs the covered rows mounted in the
render window. For very large spanning grids, keep spans modest or disable row
virtualization (virtualization={false}) on that grid so the origin cell stays
mounted while its covered rows are on screen.
See also
Related articles
- Multi-Level (Grouped) Column Headers in SvGrid - Band related columns under a shared parent header using SvGrid's nested column definition - how to nest, pin, combine with sorting and filtering, and when NOT to use grouping.
- A Custom Column Header Menu in SvGrid - Build a per-column header menu for sort, hide, pin, and custom actions using header snippets and your own dropdown component.
- A Column Show/Hide Toggle in SvGrid - Build a column chooser panel that lets users show or hide columns at runtime, with persistence across page loads and a guard against hiding everything.