ReoGrid ReoGrid Web

Merging Cells

Merging combines a rectangular range of cells into a single visual cell. Only the top-left cell’s value and style are displayed; other cells in the range are hidden.

Live Demo

Merging a range

Call merge() on any RangeHandle, then set the value and style on the top-left cell.

// Merge A1:E1 into a single cell
worksheet.range('A1:E1').merge()
worksheet.cell('A1').setValue('Q3 Sales Report').setStyle({
  bold: true,
  fontSize: 14,
  textAlign: 'center',
  backgroundColor: '#0f172a',
  color: '#f8fafc',
})

Row height is not adjusted automatically — set it manually when needed:

worksheet.row(0).height = 40

Unmerging

unmerge() splits the range back into individual cells. The original top-left value is kept; the previously hidden cells become empty.

worksheet.range('A1:E1').unmerge()

Typical patterns

Title row spanning all columns

worksheet.range('A1:F1').merge()
worksheet.cell('A1')
  .setValue('Annual Budget Overview')
  .setStyle({ bold: true, fontSize: 13, textAlign: 'center', backgroundColor: '#0f172a', color: '#f8fafc' })
worksheet.row(0).height = 36

Grouped column headers

// "H1" spanning Q1 and Q2
worksheet.range('C1:D1').merge()
worksheet.cell('C1').setValue('H1').setStyle({ bold: true, textAlign: 'center', backgroundColor: '#1e3a5f', color: '#e2e8f0' })

// "H2" spanning Q3 and Q4
worksheet.range('E1:F1').merge()
worksheet.cell('E1').setValue('H2').setStyle({ bold: true, textAlign: 'center', backgroundColor: '#1e3a5f', color: '#e2e8f0' })

Side label spanning multiple rows

worksheet.range('A2:A5').merge()
worksheet.cell('A2').setValue('North').setStyle({
  bold: true,
  textAlign: 'center',
  verticalAlign: 'middle',
  backgroundColor: '#f1f5f9',
})

Checking merge state

Use isMerged and mergeRange on a CellHandle to inspect merge state at runtime.

const cell = worksheet.cell('A1')

if (cell.isMerged) {
  const range = cell.mergeRange
  // range: { topRow, leftColumn, bottomRow, rightColumn }
  console.log(`Merged from row ${range.topRow} to ${range.bottomRow}`)
}
MemberTypeDescription
isMergedbooleantrue if this cell is part of any merged range
mergeRangeMergedCellRange | nullThe full merge range, or null if not merged

MergedCellRange uses 0-based row and column indices:

import type { MergedCellRange } from '@reogrid/lite'

const range: MergedCellRange = {
  topRow: 0,
  leftColumn: 0,
  bottomRow: 0,
  rightColumn: 4,
}

Both the anchor cell (top-left) and every covered cell return the same mergeRange.


Behavior notes

  • Only the top-left cell counts. Setting a value or style on any other cell inside a merged range has no visible effect until the range is unmerged.
  • Borders work normally. range.border() applies to the outer edges of the merged cell as expected.
  • xlsx round-trip. Merged cells are preserved when loading from xlsx files.
  • Overlapping merges are not supported. Merging a range that overlaps an existing merge will produce undefined behavior — always unmerge first.