For AI agents: the complete documentation index is available at https://gultyayev.github.io/ngx-virtual-dnd/llms.txt, the full documentation bundle is available at https://gultyayev.github.io/ngx-virtual-dnd/llms-full.txt, and this page is available as Markdown at https://gultyayev.github.io/ngx-virtual-dnd/guide/start/quick-start.md.

Quick start

This page builds a sortable list of 1,000 tasks. <vdnd-sortable-list> combines the drop target, the virtual scroller and the drop placeholder, so it is the right starting point for most apps.

Live exampleSortable list with 1,000 itemsOpen in new tab

The code

src/app/examples/quick-start/quick-start.ts
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import {
  DragPreviewComponent,
  DraggableDirective,
  DropEvent,
  DroppableGroupDirective,
  reorderItems,
  VirtualSortableListComponent,
} from 'ngx-virtual-dnd';

interface Task {
  id: string;
  title: string;
}

@Component({
  selector: 'app-quick-start-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    VirtualSortableListComponent,
    DroppableGroupDirective,
    DraggableDirective,
    DragPreviewComponent,
  ],
  template: `
    <div vdndGroup="tasks">
      <!-- Declared inside vdndGroup, so every draggable inherits the group -->
      <ng-template #taskTpl let-task>
        <div class="row" [vdndDraggable]="task.id">{{ task.title }}</div>
      </ng-template>

      <vdnd-sortable-list
        class="list"
        droppableId="backlog"
        [items]="tasks()"
        [itemHeight]="48"
        [containerHeight]="336"
        [itemIdFn]="taskId"
        [itemTemplate]="taskTpl"
        (drop)="onDrop($event)"
      />
    </div>

    <!-- Renders the element that follows the pointer. Place it once. -->
    <vdnd-drag-preview />
  `,
})
export class QuickStartExampleComponent {
  // 1,000 items; only the visible rows are in the DOM.
  readonly tasks = signal<Task[]>(
    Array.from({ length: 1000 }, (_, i) => ({ id: `task-${i + 1}`, title: `Task ${i + 1}` })),
  );

  readonly taskId = (task: Task): string => task.id;

  onDrop(event: DropEvent): void {
    reorderItems(event, this.tasks);
  }
}

What each part does

  1. vdndGroup="tasks" names the group that draggables and drop targets belong to. Items can only move between drop targets in the same group. A group is required even for a single list; without one, dragging is disabled.
  2. The item template is declared inside the vdndGroup element. Angular templates resolve dependency injection where they are declared, so this is how each draggable finds its group.
  3. [vdndDraggable]="task.id" makes each row draggable. The ID must equal what itemIdFn returns for that item, and it must sit on the template's root element.
  4. <vdnd-sortable-list> renders the visible rows. itemHeight is the row height in pixels and containerHeight is the height of the scroll area.
  5. (drop) fires when an item is dropped. The library never changes your data. reorderItems() applies the move to the tasks signal.
  6. <vdnd-drag-preview /> renders the element that follows the pointer. Place it once, anywhere in the template.
Keyboard support is built in

Every draggable can be focused with Tab. Space picks it up, the arrow keys move it, and Space or Enter drops it. See Accessibility.

Styles

The library does not style rows or the drop placeholder, so the code above renders plain text rows and an invisible placeholder. Add at least this CSS:

/* In the component's styles: the row template is declared in its template */
.row {
  display: flex;
  align-items: center;
  box-sizing: border-box;
  height: 48px; /* exactly itemHeight */
  padding: 0 16px;
  border-bottom: 1px solid #e2e7f0;
  background: #fff;
}

/* In a global stylesheet (styles.css): the library renders the placeholder */
.vdnd-drag-placeholder-visible {
  box-sizing: border-box;
  border: 1.5px dashed #0e9488;
  background: #e4f3f2;
}
Rows must be exactly itemHeight tall

With fixed heights, every row must be exactly itemHeight pixels tall, borders and padding included. Otherwise the rows on screen drift away from the positions the list calculates, and items drop in the wrong place.

For rows that differ in height, see Dynamic item heights. Styling lists every class the library adds.

Next steps