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

# Accessibility

Every draggable can be moved with the keyboard, with nothing to enable. The exception is an item that is itself a text field, a `select`, a checkbox or an editable element, which needs a focusable drag handle (see below).

## Keyboard

| Key                       | Not dragging                                                                                                       | During a keyboard drag                                                                                          |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| <kbd>Tab</kbd>            | Moves focus between draggables                                                                                     | Cancels the drag                                                                                                |
| <kbd>Space</kbd>          | Picks up the focused item (except in an item that is itself a text field, `select` or editable element; see below) | Drops                                                                                                           |
| <kbd>Enter</kbd>          | —                                                                                                                  | Drops                                                                                                           |
| <kbd>↑</kbd> <kbd>↓</kbd> | —                                                                                                                  | Moves the target position                                                                                       |
| <kbd>←</kbd> <kbd>→</kbd> | —                                                                                                                  | Moves to the neighbouring list in the same group, by on-screen position (disabled and hidden lists are skipped) |
| <kbd>Escape</kbd>         | —                                                                                                                  | Cancels. Also cancels pointer drags.                                                                            |

A hidden list is one that is still in the page but not shown: `display: none` on it or an ancestor (an inactive tab, a column hidden by a media query), `visibility: hidden`, or a size of zero. Pointer drags skip hidden lists too: the drag falls through to the list underneath, if any, as it does for a disabled list.

Pressing Space
 in a control inside an item, such as a text field or a button, types into or clicks that control and does not pick the item up, unless the control is the drag handle. The same goes for an item that is itself a text field, `textarea`
, `select`
, checkbox or editable element: Space
 types into it, toggles it or opens it, and only Space
 on a focusable drag handle inside it picks it up (see [Interactive children](https://gultyayev.github.io/ngx-virtual-dnd/guide/features/drag-behavior.md#interactive-children)
). So such an item needs one:
- An `input`, `textarea` or `select` can't hold a handle. Make a row element the draggable instead and put the control inside it.
- A `[contenteditable]` draggable without a focusable handle can't be moved with the keyboard. Give it a handle such as `<span class="grip" contenteditable="false" tabindex="0" role="button" aria-label="Drag block">`.

After a keyboard drop or cancel, focus returns to the draggable element itself. For an editable block that puts the caret in the block; press Tab
 to reach a grip inside it again.
Moving past the edge of the visible area scrolls the list to show where the item will land, so the keyboard reaches every item, not just the rendered ones. A list of plain `@for` rows scrolls the containers around it, nearest first.

## Managed for you

- `tabindex="0"` on draggables (`-1` when disabled)
- After a keyboard drag, focus returns to the moved item in its new position
- `aria-grabbed` on draggables and `aria-dropeffect="move"` on droppables

`aria-grabbed` and `aria-dropeffect` are deprecated since ARIA 1.1, and most screen readers ignore them. They do not tell screen reader users that an item can be dragged, how to drag it, or where it is. Add instructions and announcements yourself, as described below.

## Instructions

Nothing tells a screen reader user that Space
 picks an item up. Describe the keys once in a hidden element and point every draggable at it with `aria-describedby`
. Screen readers read the description when the item gets focus:
```angular-html
<p id="drag-instructions" hidden>
  Press Space to pick up. Use the arrow keys to move, Space to drop and Escape to cancel.
</p>

<ng-template #taskTpl let-task>
  <div [vdndDraggable]="task.id" aria-describedby="drag-instructions">{{ task.title }}</div>
</ng-template>
```

## Screen reader announcements

The library does not make announcements itself, so wording and translations stay in your hands. The events carry everything you need: `(dragStart)` for the pick-up, `(placeholderMove)` for every step and `(dragEnd)` for the result. Render the messages in a live region:

```ts
@Component({
  template: `
    <p id="drag-instructions" hidden>
      Press Space to pick up. Use the arrow keys to move, Space to drop and Escape to cancel.
    </p>

    <div vdndGroup="tasks">
      <ng-template #taskTpl let-task>
        <div
          [vdndDraggable]="task.id"
          [vdndDraggableData]="task"
          aria-describedby="drag-instructions"
          (dragStart)="onDragStart($event)"
          (dragEnd)="onDragEnd($event)"
        >
          {{ task.title }}
        </div>
      </ng-template>

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

    <div class="sr-only" aria-live="assertive">{{ announcement() }}</div>
  `,
})
export class TasksComponent {
  readonly #dragState = inject(DragStateService);
  readonly announcement = signal('');

  onDragStart(event: DragStartEvent): void {
    const task = event.data as Task;
    this.announcement.set(
      `Picked up ${task.title}, position ${event.sourceIndex + 1} of ${this.tasks().length}`,
    );
  }

  onPlaceholderMove(event: PlaceholderMoveEvent): void {
    // Pointer users see the placeholder move; announce keyboard steps only
    if (!this.#dragState.isKeyboardDrag()) return;
    this.announcement.set(`Position ${event.currentIndex + 1} of ${this.tasks().length}`);
  }

  onDragEnd(event: DragEndEvent): void {
    this.announcement.set(
      event.destinationIndex === null
        ? `Cancelled. Back at position ${event.sourceIndex + 1}`
        : `Dropped at position ${event.destinationIndex + 1}`,
    );
  }

  // tasks, taskId and onDrop as in the Quick start
}
```

`.sr-only` is the usual visually-hidden utility (`position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);`).

With [multiple lists](https://gultyayev.github.io/ngx-virtual-dnd/guide/essentials/multiple-lists.md), also name the list in the step message: `event.droppableId` is the list the placeholder is in.

## Touch

Touch drags work out of the box. If your lists also need to scroll by touch, add a short [`dragDelay`](https://gultyayev.github.io/ngx-virtual-dnd/guide/features/drag-behavior.md#threshold-and-delay) so a quick swipe scrolls and a press-and-hold drags.
