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

# Page-level scrolling

Sometimes the list is not its own scroll box: it scrolls together with a page header, other content and a footer. Mark the element that actually scrolls with `vdndScrollable`, and render items with `<vdnd-virtual-content>` and `*vdndVirtualFor`.


Live exampleList that scrolls with its header and footer[Open in new tab](/ngx-virtual-dnd/demo/examples/page-scroll/)

```ts file="<root>/src/app/examples/page-scroll/page-scroll.ts"
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import {
  ContentHeaderDirective,
  DraggableDirective,
  DragPreviewComponent,
  DropEvent,
  DroppableDirective,
  DroppableGroupDirective,
  reorderItems,
  ScrollableDirective,
  VirtualContentComponent,
  VirtualForDirective,
} from 'ngx-virtual-dnd';

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

@Component({
  selector: 'app-page-scroll-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    ScrollableDirective,
    VirtualContentComponent,
    VirtualForDirective,
    ContentHeaderDirective,
    DraggableDirective,
    DroppableDirective,
    DroppableGroupDirective,
    DragPreviewComponent,
  ],
  template: `
    <!-- The element that scrolls: it needs a height and overflow: auto -->
    <div class="page" vdndScrollable>
      <div vdndGroup="today">
        <vdnd-virtual-content [itemHeight]="48" vdndDroppable="today" (drop)="onDrop($event)">
          <!-- Measured automatically, and scrolls together with the rows -->
          <h3 class="page-header" vdndContentHeader>Today</h3>

          <ng-container *vdndVirtualFor="let task of tasks(); trackBy: trackById">
            <div class="row" [vdndDraggable]="task.id">{{ task.title }}</div>
          </ng-container>
        </vdnd-virtual-content>
      </div>

      <footer class="page-footer">Regular content after the list</footer>
    </div>

    <vdnd-drag-preview />
  `,
})
export class PageScrollExampleComponent {
  readonly tasks = signal<Task[]>(
    Array.from({ length: 500 }, (_, i) => ({ id: `task-${i + 1}`, title: `Task ${i + 1}` })),
  );

  readonly trackById = (_index: number, task: Task): string => task.id;

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

```

The element marked `vdndScrollable` must be the one that scrolls, so give it a height and an overflow:

```css
.page {
  height: 100vh; /* the example uses 400px */
  overflow: auto;
}
```

- `<vdnd-virtual-content>` must be inside the `vdndScrollable` element.
- Inside `<vdnd-virtual-content>`, `*vdndVirtualFor` inherits `itemHeight` and `dynamicItemHeight` from it, and the droppable ID from the enclosing `vdndDroppable`. Only `trackBy` is required.
- `vdndContentHeader` marks a header inside the component. Its height is measured and used as the list's top offset. If the header lives outside the component, pass its height with `[contentOffset]` instead.
- Auto-scroll uses the `vdndScrollable` element, so dragging near the top or bottom of the page scrolls it.

## Sticky headers and footers

A header or footer with `position: sticky` inside the scroll container stays on screen while the rows scroll behind it. The drag can't tell it covers the container, so tell it with `scrollInsetTop` and `scrollInsetBottom`: the height in pixels each covers. Measure them however suits your layout:

```angular-html
<div
  vdndScrollable
  [scrollInsetTop]="headerHeight()"
  [scrollInsetBottom]="footerHeight()"
>
  <header class="sticky-header">...</header>
  <vdnd-virtual-content [itemHeight]="72" [contentOffset]="headerHeight()" vdndDroppable="tasks">
    ...
  </vdnd-virtual-content>
  <footer class="sticky-footer">...</footer>
</div>
```

```css
.sticky-header {
  position: sticky;
  top: 0;
}
.sticky-footer {
  position: sticky;
  bottom: 0;
}
```

The drag then treats the part of the container between them as the container:

- With `constrainToContainer`, the preview stays between the header and the footer.
- Auto-scroll starts at the header's lower edge and the footer's upper edge. A pointer over either scrolls at full speed.
- The pointer over the header or footer is not over the list, as outside the container. The drop position still follows the preview's top edge, as at any edge of the container, so without `constrainToContainer` a preview pulled over the header can point at a row behind it.
- A keyboard drag scrolls the list to keep the placeholder between the header and the footer.
- A list in a scroll container nested inside this one is treated the same way where it scrolls under the header or footer: it is not targeted there, auto-scroll over them scrolls the outer container, and its constrained preview and keyboard placeholder stay clear of them.

When the header collapses or grows while the page scrolls, keep the height you pass up to date: a drag in progress measures again when it changes, and a keyboard drag scrolls its placeholder clear of them again.

Measure the insets from the container's outer (border) edges: a border on the container adds its width, and a horizontal scrollbar under a `bottom: 0` footer adds its height.

`<vdnd-sortable-list>`, `<vdnd-virtual-scroll>` and `<vdnd-virtual-viewport>` take the same inputs for content overlaid on their own rows, such as a header positioned over the top of the list. They also leave that space around their rows: the first row starts below the top inset, and the list scrolls far enough for the last row to clear the bottom one. In `<vdnd-virtual-viewport>`, the rows start `contentOffset` down when that is more than the top inset.

## Ionic

`<ion-content>` scrolls an element inside its shadow DOM, so `vdndScrollable` cannot go on `ion-content` itself. Turn off its scrolling and add a scroll host in the light DOM:

```angular-html
<ion-content [scrollY]="false">
  <div class="ion-content-scroll-host" vdndScrollable>
    <!-- vdndGroup, vdnd-virtual-content, footer... -->
  </div>
</ion-content>
```

```css
.ion-content-scroll-host {
  height: 100%;
  overflow-y: auto;
}
```

The [page scroll demo](https://gultyayev.github.io/ngx-virtual-dnd/demo/page-scroll) is a complete Ionic task manager built this way.
