Lattice Grid Buy a licence

demo D16

Row spanning

The same in the other axis, with a bounded scroll frame

cell.spanRows

Building…
Loading a live grid…

The configuration

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.13.0/lattice-grid.min.css">
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.13.0/lattice-grid.min.js"></script>

<div id="grid" style="height: 540px"></div>

<script>
  // How many rows one span is allowed to cover. Sorting by region can turn a
  // run of five into a run of fifty, so the cap keeps a single cell from
  // growing taller than the scroll frame.
  const MAX_SPAN_ROWS = 12;

  // Span a run of equal values, read from the grid's own display order rather
  // than the source array, so the run stays true after a sort or a filter.
  function spanRun(field) {
    return (p) => {
      const rows = p.grid?.rows;
      const at = p.row?.index;
      if (!rows || at == null) return 1;

      const same = (index) => {
        const row = rows.get(index);
        return row && !row.group && row.data && row.data[field] === p.value;
      };

      // Every row of a run but the first returns 0: the span is drawn once, by
      // the row at the top of it, and the others contribute no cell at all.
      if (at > 0 && same(at - 1)) return 0;

      const total = rows.count();
      let n = 1;
      while (n < MAX_SPAN_ROWS && at + n < total && same(at + n)) n++;
      return n;
    };
  }

  const grid = LatticeGrid.createGrid(document.getElementById('grid'), {
    rowKey: 'id',
    columns: [
      {
        field: 'region', title: 'Region', layout: { width: 150 },
        cell: { spanRows: spanRun('region'), align: 'center' },
      },
      { field: 'service', title: 'Service', layout: { width: 170 } },
      { field: 'window', title: 'Window' },
      { field: 'owner', title: 'Owner' },
      { field: 'hosts', title: 'Hosts', type: 'number' },
      { field: 'cost', title: 'Cost', type: 'number', format: { style: 'currency', currency: 'USD' } },
    ],
    rows,  // 400 rows in region runs of three to seven
  });
</script>

Row spanning merges a cell down through the rows beneath it so a single value stands for the group rather than repeating on every line, the vertical counterpart to column spanning. Reach for this when rows carry a shared attribute, such as a category, an owner, or a batch reference, and repeating that value on every row would bury the columns that actually vary. Lattice Grid controls this per cell through cell.spanRows, which lets a column definition decide how far a value’s cell should extend before the next distinct value begins its own span, while neighbouring columns render one value per row as usual.

The grid keeps spanning working inside a bounded scroll frame: as the viewport moves, a spanned cell that starts above the visible area still renders correctly at the top of the frame, and virtual scrolling continues to mount only the rows currently in view rather than the full spanned range. Keyboard and screen reader navigation still address each underlying row individually, so a spanned cell does not collapse multiple rows into one focus stop or hide data from assistive technology, it changes what is painted, not what is addressable. This keeps a JavaScript data grid readable when the same value repeats down a sorted or grouped column.

How do you merge cells vertically in a JavaScript data grid?

Set cell.spanRows on a column definition to tell Lattice Grid how many consecutive rows with the same value should merge into one cell. The merged cell renders once across its span while the underlying rows remain individually addressable for scrolling, sorting and keyboard navigation.