> 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.

# Drag behavior

These inputs change when a drag starts and how it moves. All of them are optional.

Only one drag runs at a time. While an item is being dragged, with the pointer or the keyboard, pressing another item (a second finger on a touch screen) or pressing Space
 on it does not start a second drag.
## Drag handles

Set `dragHandle` to a CSS selector, and only presses inside a matching element in the row start a drag (an element around the row that matches doesn't count). The rest of the row stays free for selecting text, clicking and so on.


Live exampleList with drag handles[Open in new tab](/ngx-virtual-dnd/demo/examples/drag-handle/)

```ts file="<root>/src/app/examples/drag-handle/drag-handle.ts"
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import {
  DragPreviewComponent,
  DraggableDirective,
  DropEvent,
  DroppableGroupDirective,
  reorderItems,
  VirtualSortableListComponent,
} from 'ngx-virtual-dnd';

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

@Component({
  selector: 'app-drag-handle-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    VirtualSortableListComponent,
    DroppableGroupDirective,
    DraggableDirective,
    DragPreviewComponent,
  ],
  template: `
    <div vdndGroup="playlist">
      <ng-template #trackTpl let-track>
        <!-- Only presses inside [data-drag-handle] start a drag; the rest of the row stays selectable -->
        <div class="row with-handle" [vdndDraggable]="track.id" dragHandle="[data-drag-handle]">
          <span class="handle" data-drag-handle aria-hidden="true">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
              <circle cx="9" cy="6" r="1.6" />
              <circle cx="15" cy="6" r="1.6" />
              <circle cx="9" cy="12" r="1.6" />
              <circle cx="15" cy="12" r="1.6" />
              <circle cx="9" cy="18" r="1.6" />
              <circle cx="15" cy="18" r="1.6" />
            </svg>
          </span>
          <span>{{ track.title }}</span>
        </div>
      </ng-template>

      <vdnd-sortable-list
        class="list"
        droppableId="playlist"
        [items]="tracks()"
        [itemHeight]="48"
        [containerHeight]="336"
        [itemIdFn]="trackId"
        [itemTemplate]="trackTpl"
        (drop)="onDrop($event)"
      />
    </div>

    <vdnd-drag-preview />
  `,
})
export class DragHandleExampleComponent {
  readonly tracks = signal<Track[]>(
    Array.from({ length: 300 }, (_, i) => ({ id: `track-${i + 1}`, title: `Track ${i + 1}` })),
  );

  readonly trackId = (track: Track): string => track.id;

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

```

Make the handle a non-interactive element such as a `<span>`. Presses inside a button in the row never start a drag, as the next section explains. Keyboard dragging is unaffected: the whole row stays focusable.

## Interactive children

Controls inside rows keep working: pressing them never starts a drag. What starts a drag depends on where the pointer press lands, or on which element has focus when Space
 is pressed:
| Element                                                                                                     | Pointer press starts a drag               | <kbd>Space</kbd> while it has focus picks the row up                                              |
| ----------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------- |
| The row itself, even when the draggable element is a button, such as `<button vdndDraggable>`               | Yes                                       | Yes                                                                                               |
| The row, when the draggable element is another control, such as an `input` or a `[contenteditable]` element | No, except from a drag handle (see below) | No: it types, toggles or opens instead. A focusable drag handle inside it can                     |
| A row inside a control, such as the blocks of a `[contenteditable]` editor                                  | Yes                                       | Not in Chromium (see below)                                                                       |
| The row outside the handle, when `dragHandle` is set                                                        | No                                        | Yes, unless the draggable element is a text field, `select` or editable element                   |
| A non-interactive [drag handle](#drag-handles), such as a `<span>`                                          | Yes                                       | Yes, when you make it focusable (`tabindex="0"`); otherwise it cannot take focus, and the row can |
| A drag handle that is a `button`                                                                            | No                                        | Yes                                                                                               |
| A `button`, `input`, `textarea`, `select` or `[contenteditable]` element inside the row                     | No                                        | No: it types or clicks instead                                                                    |
| An element with the `no-drag` class, and everything inside it                                               | No                                        | No                                                                                                |
| The row, with `no-drag` on the draggable element itself                                                     | No                                        | Yes                                                                                               |

Only controls inside the row count: a control around the list doesn't stop a drag, and neither does a draggable element that is a button (a `button`
, or an `input`
 of type `button`
, `submit`
, `reset`
 or `image`
). A draggable element that is another control, such as a text field, a checkbox, a `select`
 or a `[contenteditable]`
 element, keeps its own press and Space
: a press focuses, opens, toggles or places the caret, and Space
 types a space, toggles or opens. An element with `contenteditable="false"`
 is not editable, so it doesn't count either.
Rows inside a `[contenteditable]`
 editor drag like any other row, so pressing one starts a drag instead of placing the caret. To keep their text editable with the mouse, give them a [drag handle](#drag-handles)
. The same goes for blocks that are their own editing host (`<div contenteditable vdndDraggable>`
): give them a handle with `contenteditable="false"`
, such as a grip icon, and presses on it start a drag while the rest of the block stays editable. Space
 in the block's text types a space, so make the grip focusable for keyboard users, with `tabindex="0"`
, `role="button"`
 and an `aria-label`
 such as "Drag block": Space
 on it picks the block up. Without a focusable handle, the block can't be moved with the keyboard. After a keyboard drop or cancel, focus returns to the block itself (placing the caret in it), so Tab
 reaches the grip again. In Chromium, Space
 on a row inside an editor goes to the editor instead of picking the row up; Firefox and Safari pick it up.
Only the primary mouse button starts a drag. To turn dragging off completely, use [`disabled`](#disabling).

Other interactive elements are not recognized, for example a web component whose input is inside its shadow DOM, or an element with `role="switch"`. Add `no-drag` to them. For a web component, add it to the component element itself: presses inside its shadow DOM reach the row as presses on that element.

## Threshold and delay

```angular-html
<div [vdndDraggable]="task.id" [dragThreshold]="10" [dragDelay]="200">{{ task.title }}</div>
```

| Input           | Default | Effect                                                                                                                                                   |
| --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dragThreshold` | `5`     | How far (px) the pointer must move before the drag starts. Stops clicks from turning into drags.                                                         |
| `dragDelay`     | `0`     | How long (ms) the pointer must be held first. Moving past the threshold before the delay ends cancels the drag attempt, so touch users can still scroll. |

When the delay has passed, the element gets the `vdnd-drag-pending` class. Style it so users can see the item is ready to move:

```css
.vdnd-drag-pending {
  transform: scale(1.02);
  box-shadow: 0 4px 12px rgb(0 0 0 / 0.15);
}
```

A delay of 150–250 ms works well on touch devices.

## Axis locking

```angular-html
<div [vdndDraggable]="task.id" lockAxis="x">{{ task.title }}</div>
```

`lockAxis` names the axis that is **frozen**:

- `'x'`: the X axis is frozen, so the item moves only vertically
- `'y'`: the Y axis is frozen, so the item moves only horizontally

:::warning Migrating from Angular CDK
This is the opposite of CDK's `cdkDragLockAxis`, where `'x'` means the item moves along the X axis.
:::

## Constraining to the container

```angular-html
<vdnd-sortable-list [constrainToContainer]="true" ... />
<!-- or -->
<div vdndDroppable="tasks" [constrainToContainer]="true">...</div>
```

The preview cannot leave the container. Holding it against the container's top or bottom edge drops the item at the first or last position once the list is scrolled to that end; until then the drop position is the visible row at that edge, while autoscroll, when it runs, moves the list there. The container is the nearest `vdndScrollable` ancestor if there is one, and the droppable otherwise.

When a sticky header or footer inside the `vdndScrollable` element covers part of it, pass their heights as `scrollInsetTop` and `scrollInsetBottom` so the preview stays clear of them (see [Sticky headers and footers](https://gultyayev.github.io/ngx-virtual-dnd/guide/features/page-scroll.md#sticky-headers-and-footers)).

## Disabling

Every draggable, droppable and list accepts `disabled`:

```angular-html
<div [vdndDraggable]="task.id" [disabled]="task.locked">{{ task.title }}</div>

<!-- Nothing can be dropped into this list -->
<vdnd-sortable-list [disabled]="locked()" ... />
```

- A **disabled draggable** cannot be picked up. It gets `tabindex="-1"` and the `vdnd-draggable-disabled` class.
- A **disabled droppable** (or `<vdnd-sortable-list>`) is skipped as a target. Its items can still be dragged out, so for a read-only list also disable the draggables in its item template. The pointer passes through to whatever enabled droppable is underneath, and keyboard left/right navigation skips it. Releasing over it emits no `(drop)`. `(dragEnd)` still fires with `destinationIndex: null`. It gets the `vdnd-droppable-disabled` class.
