Lattice Grid Buy a licence

demo D15

Column spanning in htmx

A cell covering several columns, in its own layer so recycling cannot clip it

cell.spanColumns

Here a single cell stretches across several columns, drawn in its own layer so cell recycling never clips it. It gives a section label, a merged total or a spanning note the width it needs, without breaking the grid underneath it.

Building…
Loading a live grid…

This is the htmx version. A server-rendered table is upgraded in place, so the page is readable before any script runs and gains the full grid when the module loads, which suits a progressively enhanced app.

The configuration

<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">

<!-- A real, server-rendered table: readable with JavaScript off. data-field on
     each header names the column that cell becomes, and data-id on each row is
     the key the grid carries it under. -->
<table id="column-spanning-table" data-lattice-grid>
  <thead>
    <tr>
      <th data-field="region">Region</th>
      <th data-field="service">Service</th>
      <th data-field="note">Note</th>
      <th data-field="window">Window</th>
      <th data-field="owner">Owner</th>
      <th data-field="cost">Cost</th>
    </tr>
  </thead>
  <tbody>
    <tr data-id="M00001"><td>eu-west-1</td><td>render-0001</td><td>Region-wide maintenance: every service below is offline for the window</td><td>22:00 - 00:00</td><td>r.dubois</td><td>3858.1</td></tr>
    <tr data-id="M00002"><td>eu-west-1</td><td>queue-0002</td><td></td><td>02:00 - 04:00</td><td>a.patel</td><td>812.8</td></tr>
    <tr data-id="M00003"><td>eu-west-1</td><td>billing-0003</td><td></td><td>03:00 - 05:00</td><td>k.yilmaz</td><td>448.8</td></tr>
    <!-- the rest of the rows, rendered by your server -->
  </tbody>
</table>

<script type="module">
  import { hydrateTable } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/htmx.esm.min.js';

  // The markup carries the rows; a server-rendered table's column types are
  // declared in the config hydrateTable takes. The grid replaces the table
  // where it stood.
  const grid = hydrateTable(document.getElementById('column-spanning-table'), {
    columns: [
      { field: 'region', title: 'Region', layout: { width: 140 } },
      { field: 'service', title: 'Service', layout: { width: 160 } },
      {
        field: 'note', title: 'Note',
        layout: { width: 200 },
        // A row with a note is carrying a sentence, not a field. It runs across
        // the three remaining columns; a row without one leaves them alone and
        // the window and owner cells paint as normal.
        cell: { spanColumns: (p) => (p.value ? 3 : 1) },
      },
      { field: 'window', title: 'Window', layout: { width: 150 } },
      { field: 'owner', title: 'Owner', layout: { width: 150 } },
      { field: 'cost', title: 'Cost', type: 'number', format: { style: 'currency', currency: 'USD' } },
    ],
  });
</script>

Merging cells across columns in a JavaScript data grid

Column spanning lets a single cell stretch across several columns instead of staying confined to the one it was defined in, so a section label, a merged total, or a note covering a run of fields can occupy the width it needs. Reach for this whenever a row mixes structured data with something that belongs to no single column, such as a divider between grouped sections or a summary cell that should read across the fields it totals rather than sitting cramped in the first of them. Lattice Grid controls this through cell.spanColumns, set per cell so the decision to span is made row by row rather than fixed at the column definition, and the spanned cell renders on its own layer above the grid so column recycling during horizontal scroll cannot clip or truncate it mid-scroll.

The spanning cell’s width recalculates whenever the columns it covers are resized or reordered, without a manual redraw call, and only cells in the visible row range are evaluated for a span, so checking spanColumns costs nothing proportional to total row count under virtual scrolling. Screen readers encounter the spanned cell as one cell with a colspan attribute matching the number of columns it covers, so the merge is announced correctly rather than as a run of empty cells.

How do you merge cells across columns in a JavaScript data grid?

Set cell.spanColumns on the cell definition to the number of columns it should cover. Lattice Grid renders that cell on a separate layer so column virtualisation does not clip it, recalculates its width when the spanned columns resize or reorder, and exposes the merge to assistive technology through a matching colspan.