Aligned grids

Two independent grids sharing alignedGridGroup stay in lockstep: scroll one horizontally and the other follows; resize a column in either and the matching column resizes in both. Budget vs actuals comparison.

A live, editable Svelte 5 data grid example. Open the interactive demo or read the documentation.

What this example shows

Two independent grids that share the same `alignedGridGroup`: scroll one horizontally and the other follows; resize a column in either and the matching column resizes in both. Classic use - a "budget vs actuals" comparison, or a totals grid pinned under a body grid - where the columns must stay lined up. There are enough month columns here to force a horizontal scrollbar so the sync is obvious.

Source code (182-aligned-grids.svelte)

<!-- Documented in: docs/help/columns/aligned-grids.md -->
<script lang="ts">
  /**
   * 182. Aligned grids
   * ------------------
   * Two independent grids that share the same `alignedGridGroup`: scroll one
   * horizontally and the other follows; resize a column in either and the
   * matching column resizes in both. Classic use - a "budget vs actuals"
   * comparison, or a totals grid pinned under a body grid - where the columns
   * must stay lined up. There are enough month columns here to force a
   * horizontal scrollbar so the sync is obvious.
   */
  import { SvGrid, tableFeatures, rowSortingFeature, type ColumnDef } from '@svgrid/grid'

  const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  type Row = { category: string } & Record<string, number | string>

  function makeRows(base: number, spread: number): Row[] {
    const cats = ['Salaries', 'Marketing', 'Cloud', 'Travel', 'Office']
    return cats.map((category, ci) => {
      const r: Row = { category }
      for (let m = 0; m < MONTHS.length; m += 1) {
        r[MONTHS[m]!] = base + ((ci * 7 + m * 13) % spread) * 1000
      }
      return r
    })
  }
  const budget = makeRows(20_000, 40)
  const actuals = makeRows(18_000, 55)

  const features = tableFeatures({ rowSortingFeature })
  const money = { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } } as const

  function cols(): ColumnDef<typeof features, Row>[] {
    return [
      { field: 'category', header: 'Category', width: 150 },
      ...MONTHS.map(
        (m): ColumnDef<typeof features, Row> => ({
          field: m,
          header: m,
          width: 110,
          align: 'right',
          cellDataType: 'number',
          format: money,
        }),
      ),
    ]
  }
  // Each grid needs its own column array instance.
  const budgetCols = cols()
  const actualCols = cols()
</script>

<section class="flex flex-col flex-1 min-h-0 gap-3">
  <p class="text-sm shrink-0" style="color: var(--sg-fg);">
    <strong>Scroll horizontally</strong> in either grid - the other follows.
    <strong>Resize any month column</strong> - the matching column resizes in
    both. Both grids share <code>alignedGridGroup="finance"</code>.
  </p>

  <div class="flex flex-col gap-3 flex-1 min-h-0">
    <div class="ag-block">
      <div class="ag-title">Budget</div>
      <div class="ag-grid">
        <SvGrid responsive={true}
          data={budget}
          columns={budgetCols}
          features={features}
          alignedGridGroup="finance"
          selectionMode="none"
          showPagination={false}
          enableRowSummaries={false}
          rowHeight={36}
          containerHeight="100%"
        />
      </div>
    </div>

    <div class="ag-block">
      <div class="ag-title">Actuals</div>
      <div class="ag-grid">
        <SvGrid responsive={true}
          data={actuals}
          columns={actualCols}
          features={features}
          alignedGridGroup="finance"
          selectionMode="none"
          showPagination={false}
          enableRowSummaries={false}
          rowHeight={36}
          containerHeight="100%"
        />
      </div>
    </div>
  </div>
</section>

<style>
  .ag-block { display: flex; flex-direction: column; min-height: 0; flex: 1; }
  .ag-title {
    font-size: 12px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--sg-muted, #64748b);
    padding: 2px 2px 6px;
  }
  .ag-grid { flex: 1; min-height: 0; }
</style>

View this example on GitHub

Related documentation

More Columns examples

  • Column pinning + freezing - Wide 13-column grid. Pin Company left and Price right via the column menu; the middle scrolls under sticky edges.
  • Columns hierarchy + manager - Side-panel tree of grouped columns: drag leaves to reorder, click a chevron to collapse a group into one summary column, toggle visibility per leaf or whole group.
  • Column layout API - setColumnWidth + setColumnPinning + getColumnWidths + getColumnPinning. Save the snapshot to localStorage, restore on reload, drive widths and pins from buttons.
  • Tool panel (Columns + Filters) - The docked enterprise sidebar, two tabs. Columns: toggle visibility, reorder up/down, group by a column. Filters: an operator + value control per column (numeric operators come free via cellDataType), kept in sync with the column menu. Enable with the toolPanel prop.
  • Column reorder - Set enableColumnReorder on <SvGrid> and every header becomes draggable, with a drop indicator. api.setColumnOrder / getColumnOrder + onColumnOrderChange event; persist to restore across reloads.