Lattice Grid Buy a licence

demo D143

Pinned columns and scroll extent

A centre column stays reachable behind a pinned region

pinned regions · horizontal extent

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>
  const grid = LatticeGrid.createGrid(document.getElementById('grid'), {
    rowKey: 'id',
    selection: 'multiple',
    toolPanel: {
      side: 'left',
      panels: ['columns', 'filters', 'views', 'quick'],
      actions: ['undo', 'redo', 'export', 'restore', 'maximise'],
      exportName: 'lattice-demo',
    },
    // Every centre column is given a fixed width so the total is far wider than
    // any viewport; one column is pinned left and two are pinned right.
    columns: [
      { field: 'service', title: 'Service', layout: { pin: 'start', width: 150 } },
      { field: 'team', title: 'Team', filter: { type: 'set' }, layout: { width: 150 } },
      { field: 'owner', title: 'Owner', filter: { type: 'set' }, layout: { width: 170 } },
      { field: 'tier', title: 'Tier', layout: { width: 190 },
        cell: { decoration: 'pill', variant: { map: { gold: 'warning', silver: 'neutral', bronze: 'info' } } } },
      { field: 'state', title: 'State', layout: { width: 130 },
        cell: { decoration: 'dot', variant: { map: { healthy: 'success', degraded: 'warning', failing: 'danger', unknown: 'neutral' } } } },
      { field: 'region', title: 'Region', filter: { type: 'set' }, layout: { width: 150 } },
      { field: 'active', title: 'Active', type: 'boolean', layout: { width: 170 } },
      { field: 'deprecated', title: 'Deprecated', type: 'boolean', layout: { width: 190 } },
      { field: 'version', title: 'Version', layout: { width: 130 } },
      { field: 'instances', title: 'Instances', type: 'number', layout: { width: 150 } },
      { field: 'cpuCores', title: 'vCPU', type: 'number', layout: { width: 170 } },
      { field: 'memoryGb', title: 'Memory GB', type: 'number', layout: { width: 190 } },
      { field: 'diskBytes', title: 'Disk', type: 'number', format: { notation: 'compact' }, layout: { width: 130 } },
      { field: 'requests', title: 'Requests', type: 'number', format: { notation: 'compact' }, layout: { width: 150 } },
      { field: 'errors', title: 'Errors', type: 'number', layout: { width: 170 } },
      { field: 'errorRate', title: 'Error rate', type: 'number', format: { style: 'percent', decimals: 2 }, layout: { width: 190 } },
      { field: 'p50Ms', title: 'p50 ms', type: 'number', layout: { width: 130 } },
      { field: 'p95Ms', title: 'p95 ms', type: 'number', layout: { width: 150 } },
      { field: 'p99Ms', title: 'p99 ms', type: 'number', layout: { width: 170 } },
      { field: 'uptime', title: 'Uptime', type: 'number', format: { style: 'percent', decimals: 3 }, layout: { width: 190 } },
      { field: 'monthlyCost', title: 'Monthly cost', type: 'number', format: { style: 'currency', currency: 'USD' }, total: 'sum', layout: { pin: 'end', width: 150 } },
      { field: 'budget', title: 'Budget', type: 'number', format: { style: 'currency', currency: 'USD' }, total: 'sum', layout: { width: 150 } },
      { field: 'headcount', title: 'Headcount', type: 'number', total: 'sum', layout: { width: 170 } },
      { field: 'openIncidents', title: 'Incidents', type: 'number', total: 'sum', layout: { pin: 'end', width: 120 } },
      { field: 'lastDeploy', title: 'Last deploy', type: 'dateString', layout: { width: 130 } },
      { field: 'created', title: 'Created', type: 'dateString', layout: { width: 150 } },
      { field: 'slaTarget', title: 'SLA', type: 'number', format: { style: 'percent', decimals: 2 }, layout: { width: 170 } },
      { field: 'onCall', title: 'On call', layout: { width: 190 } },
      { field: 'repository', title: 'Repository', layout: { width: 130 } },
      { field: 'notes', title: 'Notes', layout: { width: 150 } },
    ],
    rows,  // 20,000 wide service records
  });
</script>

Keeping a centre column reachable when pinned regions expand

Pinned regions, sometimes called frozen columns, hold selected fields fixed at the left or right edge of the grid while the remaining columns scroll underneath. A developer reaches for this once a row carries more fields than fit on screen and a handful, an identifier or a totals column, need to stay visible regardless of scroll position. The setting that matters is horizontal extent: as pinned regions grow wider, the scrollable centre column has to keep enough room to stay reachable rather than being squeezed to nothing or pushed out of view. Lattice Grid tracks this through the pinned regions · horizontal extent mechanism, which recalculates the scrollable width whenever a pinned region is resized, added, or removed, so the centre never collapses below a usable span even with both sides pinned at once. Because a JavaScript data grid rendering pinned and scrollable regions has to keep their row heights synchronised, the same layout pass that adjusts extent also keeps the two regions aligned pixel for pixel while scrolling, so a row never appears to split across a seam. Keyboard navigation crosses the pinned boundary in a single tab stop, moving focus from the last pinned column into the scrollable region without an intermediate stop on a column that is not there, which matters for anyone navigating without a pointer.

What happens to scrolling when pinned columns take up most of the grid width?

Lattice Grid recalculates the scrollable centre region’s width whenever pinned columns are added, resized, or removed, so it keeps a usable extent rather than shrinking to zero. If pinned regions on both sides leave too little room for the centre column, the grid still renders it as a distinct scrollable area, keeping every field reachable regardless of how much width the pinned regions claim.