Multi-grid sync

Two grids over one $state array - edits on the left propagate to the right instantly; each grid keeps its own filter + sort.

A live, editable Svelte 5 data grid example from the SvGrid gallery (Master-Detail & Forms). See the SvGrid documentation for the full API.

About this example

Two Svelte 5 data grids over a single $state array. Edit a cell in either grid and the other re-renders immediately because both read the same reactive reference, with no grid-to-grid wiring. Filter and sort state stay per grid, so each keeps its own view of the shared inventory rows.

Two grids over a single Svelte 5 $state array. Edit a cell in either - the other re-renders immediately because both grids read the same reactive reference.

No grid-to-grid wiring is needed; this is the natural Svelte pattern. The demo also shows that filter / sort state is per-grid (each grid's view is independent) while the data they project is shared.

Imports, features and API used

Imports: @svgrid/grid

Table features registered: rowSortingFeature, columnFilteringFeature

Columns: sku (SKU), product (Product), warehouse (Warehouse), qty (Qty), reorder (Reorder pt)

Frequently asked questions

How do the grids stay in sync?

They are given the same $state array as data. An inline edit in one grid mutates the row object, and Svelte's reactivity re-renders the other grid's cell; nothing else is needed.

Why do the filters not affect both grids?

Sorting and filtering are internal grid state, held per instance. Each grid projects the shared rows through its own pipeline, so one can show a filtered subset while the other shows everything.

Does this work with the headless engine too?

Yes. Two createSvGrid engines given the same array behave the same way; the shared-state headless demo goes further and shares the sort state as well.

Related documentation

Related articles

Source code (70-multi-grid-sync.svelte)

<script lang="ts">
  /**
   * 70. Multi-grid sync - shared $state
   * ----------------------------------
   * Two grids over a single Svelte 5 `$state` array. Edit a cell in
   * either - the other re-renders immediately because both grids read
   * the same reactive reference.
   *
   * No grid-to-grid wiring is needed; this is the natural Svelte
   * pattern. The demo also shows that filter / sort state is per-grid
   * (each grid's view is independent) while the data they project is
   * shared.
   */
  import {
    SvGrid,
    tableFeatures,
    rowSortingFeature,
    columnFilteringFeature,
    type GridColumns,
  } from '@svgrid/grid'

  type Inventory = {
    id: string
    sku: string
    product: string
    warehouse: 'East' | 'West' | 'Central'
    qty: number
    reorder: number
  }

  let rows = $state<Inventory[]>([
    { id: 'i01', sku: 'A-100', product: 'Steel sheet 1/2"', warehouse: 'East',    qty: 240, reorder: 100 },
    { id: 'i02', sku: 'A-101', product: 'Steel sheet 3/4"', warehouse: 'West',    qty:  80, reorder: 100 },
    { id: 'i03', sku: 'B-200', product: 'Stainless rivets', warehouse: 'East',    qty: 6800, reorder: 1000 },
    { id: 'i04', sku: 'B-201', product: 'Brass rivets',     warehouse: 'Central', qty: 1200, reorder: 1500 },
    { id: 'i05', sku: 'C-300', product: 'Drill bit set',    warehouse: 'West',    qty:  44, reorder:  20 },
    { id: 'i06', sku: 'C-301', product: 'Impact driver',    warehouse: 'Central', qty:  12, reorder:  10 },
    { id: 'i07', sku: 'D-400', product: 'Wire rope 1/4"',   warehouse: 'East',    qty:  60, reorder: 100 },
    { id: 'i08', sku: 'D-401', product: 'Wire rope 3/8"',   warehouse: 'West',    qty: 180, reorder: 100 },
  ])

  const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })

  const columns: GridColumns<Inventory> = [
    { field: 'sku',       header: 'SKU',       editorType: 'text',   width: 100 },
    { field: 'product',   header: 'Product',   editorType: 'text',   width: 180 },
    { field: 'warehouse', header: 'Warehouse', editorType: 'list',
      editorOptions: ['East', 'West', 'Central'],
      width: 140,
    },
    { field: 'qty',       header: 'Qty',       editorType: 'number', width: 100 },
    { field: 'reorder',   header: 'Reorder pt',editorType: 'number', width: 130 },
  ]
</script>

<section class="flex flex-col flex-1 min-h-0 gap-3">
  <p class="sync-note text-sm shrink-0">
    Both grids read from the same <code>$state</code> array. Edit any
    <code>Qty</code> or <code>Warehouse</code> on the left - the right
    grid updates instantly. Each grid keeps its OWN filter + sort.
  </p>

  <div class="grid grid-cols-1 md:grid-cols-2 gap-4 flex-1 min-h-0">
    <div class="flex flex-col gap-2 min-h-0">
      <div class="sync-title text-sm font-medium">
        Operations view <span class="sync-sub font-normal">- editable</span>
      </div>
      <div class="flex-1 min-h-0">
        <SvGrid responsive={true}
      columnResize
          data={rows}
          columns={columns}
          features={features}
          filterMode="menu"
          selectionMode="cell"
          showRowNumbers={false}
          enableInlineEditing={true}
          enableCellSelection={true}
          rowHeight={32}
          containerHeight="100%"
          fitColumns={true}
        />
      </div>
    </div>

    <div class="flex flex-col gap-2 min-h-0">
      <div class="sync-title text-sm font-medium">
        Analytics view <span class="sync-sub font-normal">- read-only mirror</span>
      </div>
      <div class="flex-1 min-h-0">
        <SvGrid responsive={true}
      columnResize
          data={rows}
          columns={columns}
          features={features}
          filterMode="menu"
          selectionMode="cell"
          showRowNumbers={false}
          enableInlineEditing={false}
          enableCellSelection={true}
          enableRowSummaries={true}
          rowHeight={32}
          containerHeight="100%"
          fitColumns={true}
        />
      </div>
    </div>
  </div>
</section>

<style>
  /* Caption chrome follows the active grid theme. */
  .sync-note { color: var(--sg-muted, #64748b); }
  .sync-title { color: var(--sg-fg, #334155); }
  .sync-sub { color: var(--sg-muted, #64748b); }
</style>

View this example on GitHub

More Master-Detail & Forms examples

  • Forms-in-grid (master/detail) - Master grid + tabbed detail form. Dirty tracking, inline validation, atomic Save / Discard, contact subgrid.
  • Side-drawer edit form - Linear / Notion pattern: click a row, a polished drawer slides in with the full record, dirty-state badge, live validation, Esc / Ctrl+Enter shortcuts.
  • Detail rows (expandable) - Stripe / GitHub-style inline row expansion: click the chevron to reveal a 4-panel detail (line items, shipping, payments, support thread).
  • Master / detail (nested grid) - The classic master/detail: expand any account row to reveal a full nested SvGrid of its call records. Built on isDetailRow + renderDetailRow - a real full-width detail row hosting another grid.