Selection + copy/paste

Row + cell-range selection with TSV clipboard round-trip.

A live, editable Svelte 5 data grid example from the SvGrid gallery (Selection & Clipboard). See the SvGrid documentation for the full API.

About this example

Row and cell-range selection in the Svelte 5 data grid with a built-in TSV clipboard. selectionMode='both' gives row checkboxes and rectangular cell selection at once; Ctrl or Cmd+C copies the active rectangle as tab-separated text and Ctrl+V pastes TSV back into the same range, so data moves straight into and out of a spreadsheet. A hand-rolled footer summarises the current selection from onRowSelectionChange.

Row checkboxes (selectionMode='both') + cell-range selection. Built-in clipboard:

  • Ctrl/Cmd+C copies the active rectangular selection as TSV
  • Ctrl/Cmd+V pastes TSV from the clipboard into the same range The selection summary footer below the grid is rolled by hand - the grid exposes the selected-cell rectangle via the active-cell state.

Imports, features and API used

Imports: @svgrid/grid, ../shared/seed

Table features registered: rowSortingFeature, rowSelectionFeature

Columns: firstName (First name), lastName (Last name), department (Department), country (Country), age (Age), salary (Salary), performance (Performance)

Frequently asked questions

How do I copy cells out of the grid?

Select a rectangle with click and drag or Shift and the arrow keys, then press Ctrl or Cmd+C. The grid writes the range to the clipboard as TSV, which spreadsheets paste as cells. With contextMenu on, the right-click menu offers copy, cut, paste and clear as well.

Does paste work from a spreadsheet?

Yes. Copy cells in a spreadsheet, select the target cell in the grid and press Ctrl or Cmd+V. The TSV is split into cells starting at the active cell; inline editing is off here so the demo is read-only, turn it on to write.

How do I get the selected rows?

onRowSelectionChange is called with the selection state and the selected row objects; the demo stores the rows and renders the footer from them.

Related documentation

Related articles

Source code (04-selection-copy-paste.svelte)

<!-- Documented in: docs/help/recipes.md -->
<script lang="ts">
  /**
   * 04. Selection + copy/paste
   * --------------------------
   * Row checkboxes (selectionMode='both') + cell-range selection.
   * Built-in clipboard:
   *   - Ctrl/Cmd+C copies the active rectangular selection as TSV
   *   - Ctrl/Cmd+V pastes TSV from the clipboard into the same range
   * The selection summary footer below the grid is rolled by hand -
   * the grid exposes the selected-cell rectangle via the active-cell state.
   */
  import {
    SvGrid,
    tableFeatures,
    rowSortingFeature,
    rowSelectionFeature,
    type GridColumns,
    type SvGridApi,
  } from '@svgrid/grid'
  import { makePeople, type Person } from '../shared/seed'

  const features = tableFeatures({
    rowSortingFeature,
    rowSelectionFeature,
  })

  const rows = makePeople(80)

  const columns: GridColumns<Person> = [
    { field: 'firstName',  header: 'First name', editorType: 'text' },
    { field: 'lastName',   header: 'Last name',  editorType: 'text' },
    { field: 'department', header: 'Department', editorType: 'text' },
    { field: 'country',    header: 'Country',    editorType: 'text' },
    {
      field: 'age',
      header: 'Age',
      editorType: 'number',
    },
    {
      field: 'salary',
      header: 'Salary',
      editorType: 'number',
      format: { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } },
    },
    {
      field: 'performance',
      header: 'Performance',
      editorType: 'number',
    },
  ]

  let api = $state<SvGridApi<typeof features, Person> | null>(null)
  let selectedRows = $state<Person[]>([])

  const stats = $derived.by(() => {
    let sumSalary = 0
    let perfTotal = 0
    for (const row of selectedRows) {
      sumSalary += row.salary
      perfTotal += row.performance
    }
    return {
      count: selectedRows.length,
      sumSalary,
      avgPerf: selectedRows.length ? Math.round(perfTotal / selectedRows.length) : 0,
    }
  })

  const currency = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 })
</script>

<section class="flex flex-col flex-1 min-h-0 gap-3">
  <div class="flex flex-wrap items-center gap-3 text-sm shrink-0">
    <span class="text-slate-600 dark:text-slate-300">
      Tick the row checkboxes, or click + drag cells to make a range selection.
      <kbd>Ctrl/Cmd+C</kbd> copies as TSV.
    </span>
  </div>

  <div class="flex-1 min-h-0">
    <SvGrid responsive={true}
      columnResize
      data={rows}
      columns={columns}
      features={features}
      filterMode="menu"
      selectionMode="both"
      enableInlineEditing={false}
      enableCellSelection={true}
      contextMenu={true}
      rowHeight={36}
      containerHeight="100%"
      fitColumns={true}
      onApiReady={(next) => (api = next)}
      onRowSelectionChange={(_, rows) => (selectedRows = rows)}
    />
  </div>

  <footer class="sel-summary rounded border px-4 py-2 text-sm flex flex-wrap items-center gap-6 shrink-0">
    <span><strong>{stats.count}</strong> rows selected</span>
    <span>Σ Salary: <strong>{currency.format(stats.sumSalary)}</strong></span>
    <span>⌀ Performance: <strong>{stats.avgPerf}</strong></span>
  </footer>
</section>

<style>
  /* Theme tokens rather than fixed slate utilities, so the summary bar tracks
     whichever preset the gallery is running. */
  .sel-summary {
    background: var(--sg-bg-subtle, var(--sg-header-bg, #f8fafc));
    border-color: var(--sg-border, #e2e8f0);
    color: var(--sg-fg, #0f172a);
  }
</style>

View this example on GitHub

More Selection & Clipboard examples

  • Bulk-action bar - selectionBar floats a bar over the grid while rows are selected - the count, your actions, an overflow menu, and Clear - the pattern an issue tracker uses for bulk edit. position pins it to the bottom (default) or top edge. Actions receive the selected rows in display order, and hidden / disabled re-check against the live selection so buttons appear and grey out as the set changes.
  • Selection API + events - Drive cell selection with api.selectCells / api.getSelected; subscribe to changes via onCellSelectionChange. Live SUM/AVG/MIN/MAX panel + event log + copy-as-TSV.
  • Status bar (range aggregates) - Excel-style bar under the grid with live aggregates of the selected cell range: Count, Sum, Avg, Min, Max. Enable with statusBar + enableCellSelection, then drag a rectangle across numeric cells. Choose the aggregate set via statusBar={ aggregates }.
  • Range selection (Excel-style) - Drag any rectangle of cells, or Ctrl/Cmd+drag to add MORE ranges - all stay highlighted and copy together. Toolbar issues common ranges; live SUM/AVG/MIN/MAX/COUNT status bar; copy as TSV.
  • Bulk actions toolbar - Select rows → sticky action bar with Mark / Delete / Copy as TSV. The Gmail / Linear pattern.