External drop zones (row drag)

Drag a row out of the grid onto any element - an Archive or Delete bucket - via the rowDropZone action. The row leaves the grid and the zone's onDrop handles it. In-grid reorder still works.

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

What this example shows

Managed row drags can be dropped onto ANY element outside the grid via the `rowDropZone` action. Here, drag a task out of the grid onto Archive or Delete: the row leaves the grid, and the zone's `onDrop` handles it. The grid keeps `rowDragManaged` (so in-grid reorder still works) and a `rowDragGroup` the zones match on.

Source code (184-external-drop-zone.svelte)

<!-- Documented in: docs/help/rows/row-dragging.md -->
<script lang="ts">
  /**
   * 184. External row-drag drop zones
   * ---------------------------------
   * Managed row drags can be dropped onto ANY element outside the grid via the
   * `rowDropZone` action. Here, drag a task out of the grid onto Archive or
   * Delete: the row leaves the grid, and the zone's `onDrop` handles it. The
   * grid keeps `rowDragManaged` (so in-grid reorder still works) and a
   * `rowDragGroup` the zones match on.
   */
  import { SvGrid, rowDropZone, type ColumnDef } from '@svgrid/grid'

  type Task = { id: number; title: string; priority: 'High' | 'Medium' | 'Low' }

  let tasks = $state<Task[]>([
    { id: 1, title: 'Design empty states', priority: 'Medium' },
    { id: 2, title: 'Keyboard nav audit', priority: 'High' },
    { id: 3, title: 'Dark theme polish', priority: 'Low' },
    { id: 4, title: 'CSV export edge cases', priority: 'Medium' },
    { id: 5, title: 'Virtualization stress test', priority: 'High' },
  ])
  let archived = $state<Task[]>([])
  let lastAction = $state('Drag a row onto Archive or Delete.')

  const columns: ColumnDef<any, Task>[] = [
    { field: 'title', header: 'Task', width: 240 },
    { field: 'priority', header: 'Priority', width: 110 },
  ]
</script>

<section class="flex flex-col flex-1 min-h-0 gap-3">
  <p class="text-sm shrink-0" style="color: var(--sg-fg);">
    Drag a task by its grip out onto a <strong>drop zone</strong>. The row leaves
    the grid and the zone's <code>onDrop</code> runs - Archive keeps it, Delete
    discards it. In-grid reorder still works.
  </p>

  <div class="edz-status shrink-0">{lastAction}</div>

  <div class="edz-layout flex-1 min-h-0">
    <div class="edz-grid">
      <SvGrid responsive={true}
        data={tasks}
        columns={columns}
        showRowNumbers={true}
        rowDragManaged={true}
        rowDragGroup="tasks"
        rowHeight={40}
        containerHeight="100%"
        fitColumns={true}
      />
    </div>

    <div class="edz-zones">
      <div
        class="edz-zone edz-archive"
        use:rowDropZone={{
          group: 'tasks',
          onDrop: (e) => { archived = [...archived, e.row as Task]; lastAction = `Archived "${(e.row as Task).title}"` },
        }}
      >
        <div class="edz-zone-title">📥 Archive</div>
        <div class="edz-zone-hint">Drop to archive ({archived.length})</div>
        {#if archived.length}
          <ul class="edz-archived">
            {#each archived as t (t.id)}<li>{t.title}</li>{/each}
          </ul>
        {/if}
      </div>

      <div
        class="edz-zone edz-delete"
        use:rowDropZone={{
          group: 'tasks',
          onDrop: (e) => { lastAction = `Deleted "${(e.row as Task).title}"` },
        }}
      >
        <div class="edz-zone-title">🗑️ Delete</div>
        <div class="edz-zone-hint">Drop to remove</div>
      </div>
    </div>
  </div>
</section>

<style>
  .edz-status {
    font-size: 12px; color: var(--sg-fg);
    background: var(--sg-header-bg); border: 1px solid var(--sg-border);
    border-radius: 6px; padding: 8px 12px;
  }
  .edz-layout { display: grid; grid-template-columns: 1fr 240px; gap: 16px; }
  .edz-grid { min-height: 0; }
  .edz-zones { display: flex; flex-direction: column; gap: 12px; }
  .edz-zone {
    flex: 1; display: flex; flex-direction: column; gap: 4px;
    border: 2px dashed var(--sg-border, #cbd5e1); border-radius: 10px;
    padding: 14px; text-align: center; color: var(--sg-muted, #64748b);
    transition: border-color 120ms ease, background-color 120ms ease;
  }
  .edz-zone-title { font-size: 15px; font-weight: 700; color: var(--sg-fg, #0f172a); }
  .edz-zone-hint { font-size: 11px; }
  .edz-delete { border-color: color-mix(in oklab, #ef4444 40%, var(--sg-border)); }
  .edz-archived {
    list-style: none; margin: 6px 0 0; padding: 0; text-align: left;
    font-size: 11px; color: var(--sg-fg); overflow: auto;
  }
  .edz-archived li { padding: 2px 6px; border-radius: 4px; background: color-mix(in oklab, #10b981 10%, transparent); margin-bottom: 3px; }
</style>

View this example on GitHub

Related documentation

More Rows & Cells examples

  • Managed row dragging (grid-to-grid) - Reorder rows by dragging their grip, or move a row from one grid into another - both grids share a rowDragGroup, so the row leaves the source and lands in the target. The grid mutates its own data on drop and fires onRowDragEnd on the receiver.
  • Custom cells + themes - Avatars, sparklines, progress bars, density toggle, dark mode, full a11y.
  • Sparkline cells - In-cell mini charts as a first-class column type: set `sparkline` on a number-array column and the grid paints an inline SVG. Line, area, bar (with +/- coloring), and win/loss - no chart library, no custom snippet.
  • Conditional formatting (engine) - Excel-style value-driven cell coloring as a declarative `conditionalFormats` engine prop: gradient heat maps (alpha ramp, zero-centred, banded, column-comparison), in-cell data bars, icon sets, and predicate rules - scoped per column, no per-cell snippet.
  • Conditional styling - Support-ticket triage board: rowClass highlights SLA breach + at-risk rows with side-bar accents; cellClass paints priority pills, status badges, agent-load progress bars, and CSAT highlights.