> Markdown version of [Drag and Drop](https://vaadin.com/docs/next/components/grid/drag-drop). Section index: [llms.txt](https://vaadin.com/docs/next/components/llms.txt)

# Grid Drag & Drop

Grid supports drag-and-drop actions. This feature might be used, for example, to reorder rows and to drag rows between grids.

## <a id="drop-mode"></a>Drop Mode

The drop mode of a grid determines where a drop can happen. Vaadin offers four different drop modes, which are described in the table here:

| Drop Mode                           | Description                                                                                                                                                                |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [On Grid](#drag-rows-between-grids) | Drops can occur on the grid as a whole, not on top of rows or between individual rows. Use this mode when the order isn’t important.                                       |
| [Between](#row-reordering)          | Drops can happen between rows. Use this mode when the order is important.                                                                                                  |
| [On Top](#drag-and-drop-filters)    | Drops can take place on top of rows. This is useful when creating relationships between items or moving an item into another item, such as placing a file inside a folder. |
| On Top or Between                   | Drops can occur on top of rows or between them.                                                                                                                            |

## <a id="row-reordering"></a>Row Reordering

You can drag rows to reorder them. This can be a useful and impressive feature for users. Try dragging with your mouse one of the rows of data in the example here to another place in the list.

**Lit** — `grid-row-reordering.ts`

```typescript
@customElement('grid-row-reordering')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    applyTheme(root);
    return root;
  }

  @state()
  private items: Person[] = [];

  @state()
  private draggedItem: Person | undefined;

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people;
  }

  protected override render() {
    return html`
      <vaadin-grid
        .items="${this.items}"
        rows-draggable
        .dropMode=${this.draggedItem ? 'between' : undefined}
        @grid-dragstart="${(event: GridDragStartEvent<Person>) => {
          this.draggedItem = event.detail.draggedItems[0];
        }}"
        @grid-dragend="${() => {
          this.draggedItem = undefined;
        }}"
        @grid-drop="${(event: GridDropEvent<Person>) => {
          const { dropTargetItem, dropLocation } = event.detail;
          // Only act when dropping on another item
          if (this.draggedItem && dropTargetItem !== this.draggedItem) {
            // Remove the item from its previous position
            const draggedItemIndex = this.items.indexOf(this.draggedItem);
            this.items.splice(draggedItemIndex, 1);
            // Re-insert the item at its new position
            const dropIndex =
              this.items.indexOf(dropTargetItem) + (dropLocation === 'below' ? 1 : 0);
            this.items.splice(dropIndex, 0, this.draggedItem);
            // Re-assign the array to refresh the grid
            this.items = [...this.items];
          }
        }}"
      >
        <vaadin-grid-column
          header="Image"
          flex-grow="0"
          auto-width
          ${columnBodyRenderer(this.avatarRenderer, [])}
        ></vaadin-grid-column>
        <vaadin-grid-column path="firstName"></vaadin-grid-column>
        <vaadin-grid-column path="lastName"></vaadin-grid-column>
        <vaadin-grid-column path="email"></vaadin-grid-column>
      </vaadin-grid>
    `;
  }

  private avatarRenderer: GridColumnBodyLitRenderer<Person> = (person) => html`
    <vaadin-avatar
      img="${person.pictureUrl}"
      name="${person.firstName} ${person.lastName}"
    ></vaadin-avatar>
  `;
}
```

**Flow** — `GridRowReordering.java`

```java
Grid<Person> grid = setupGrid();

// Modifying the data view requires a mutable collection
List<Person> people = new ArrayList<>(DataService.getPeople());
GridListDataView<Person> dataView = grid.setItems(people);

grid.setRowsDraggable(true);

grid.addDragStartListener(e -> {
    draggedItem = e.getDraggedItems().get(0);
    grid.setDropMode(GridDropMode.BETWEEN);
});

grid.addDropListener(e -> {
    Person targetPerson = e.getDropTargetItem().orElse(null);
    GridDropLocation dropLocation = e.getDropLocation();

    boolean personWasDroppedOntoItself = draggedItem
            .equals(targetPerson);

    if (targetPerson == null || personWasDroppedOntoItself)
        return;

    dataView.removeItem(draggedItem);

    if (dropLocation == GridDropLocation.BELOW) {
        dataView.addItemAfter(draggedItem, targetPerson);
    } else {
        dataView.addItemBefore(draggedItem, targetPerson);
    }
});

grid.addDragEndListener(e -> {
    draggedItem = null;
    grid.setDropMode(null);
});
```

**React** — `grid-row-reordering.tsx`

```tsx
function handleDragStart(event: GridDragStartEvent<Person>): void {
  draggedItem.value = event.detail.draggedItems[0];
}

function handleDragEnd(): void {
  draggedItem.value = undefined;
}

function handleDrop(event: GridDropEvent<Person>): void {
  const { dropTargetItem, dropLocation } = event.detail;
  // Only act when dropping on another item
  if (draggedItem.value && dropTargetItem !== draggedItem.value) {
    // Remove the item from its previous position
    const draggedItemIndex = items.value.indexOf(draggedItem.value);
    items.value.splice(draggedItemIndex, 1);
    // Re-insert the item at its new position
    const dropIndex = items.value.indexOf(dropTargetItem) + (dropLocation === 'below' ? 1 : 0);
    items.value.splice(dropIndex, 0, draggedItem.value);
    // Re-assign the array to refresh the grid
    items.value = [...items.value];
  }
}

return (
  <Grid
    items={items.value}
    rowsDraggable
    dropMode={draggedItem.value ? 'between' : undefined}
    onGridDragstart={handleDragStart}
    onGridDragend={handleDragEnd}
    onGridDrop={handleDrop}
  >
    <GridColumn header="Image" flexGrow={0} autoWidth renderer={avatarRenderer} />

    <GridColumn path="firstName" />
    <GridColumn path="lastName" />
    <GridColumn path="email" />
  </Grid>
);
```

## <a id="drag-rows-between-grids"></a>Drag Rows between Grids

Rows can be dragged from one grid to another. You might use this feature to move, copy or link items from different datasets.

In the example here, there are two grids of data. Maybe they represent people to speak at two different presentations at the same conference. One grid lists the first panel of speakers and the other the second panel. Try dragging people from one to the other, as if you were reassigning them to speak at a different panel.

**Lit** — `grid-drag-rows-between-grids.ts`

```typescript
@state()
private draggedItem: Person | undefined;

@state()
private grid1Items: Person[] = [];

@state()
private grid2Items: Person[] = [];

protected override async firstUpdated() {
  const { people } = await getPeople({ count: 10 });
  this.grid1Items = people.slice(0, 5);
  this.grid2Items = people.slice(5);
}

private startDraggingItem = (event: GridDragStartEvent<Person>) => {
  this.draggedItem = event.detail.draggedItems[0];
};

private clearDraggedItem = () => {
  this.draggedItem = undefined;
};

protected override render() {
  return html`
    <div class="grids-container">
      <vaadin-grid
        .items="${this.grid1Items}"
        rows-draggable
        .dropMode=${this.draggedItem ? 'on-grid' : undefined}
        @grid-dragstart="${this.startDraggingItem}"
        @grid-dragend="${this.clearDraggedItem}"
        @grid-drop="${() => {
          const draggedPerson = this.draggedItem!;
          const draggedItemIndex = this.grid2Items.indexOf(draggedPerson);
          if (draggedItemIndex >= 0) {
            // Remove the item from its previous position
            this.grid2Items.splice(draggedItemIndex, 1);
            // Re-assign the array to refresh the grid
            this.grid2Items = [...this.grid2Items];
            // Re-assign the array to refresh the grid
            this.grid1Items = [...this.grid1Items, draggedPerson];
          }
        }}"
      >
        <vaadin-grid-column
          header="Full name"
          ${columnBodyRenderer<Person>(
            (person) => html`${person.firstName} ${person.lastName}`,
            []
          )}
        ></vaadin-grid-column>
        <vaadin-grid-column path="profession"></vaadin-grid-column>
      </vaadin-grid>

      <vaadin-grid
        .items="${this.grid2Items}"
        rows-draggable
        .dropMode=${this.draggedItem ? 'on-grid' : undefined}
        @grid-dragstart="${this.startDraggingItem}"
        @grid-dragend="${this.clearDraggedItem}"
        @grid-drop="${() => {
          const draggedPerson = this.draggedItem!;
          const draggedItemIndex = this.grid1Items.indexOf(draggedPerson);
          if (draggedItemIndex >= 0) {
            // Remove the item from its previous position
            this.grid1Items.splice(draggedItemIndex, 1);
            // Re-assign the array to refresh the grid
            this.grid1Items = [...this.grid1Items];
            // Re-assign the array to refresh the grid
            this.grid2Items = [...this.grid2Items, draggedPerson];
          }
        }}"
      >
        <vaadin-grid-column
          header="Full name"
          ${columnBodyRenderer<Person>(
            (person) => html`${person.firstName} ${person.lastName}`,
            []
          )}
        ></vaadin-grid-column>
        <vaadin-grid-column path="profession"></vaadin-grid-column>
      </vaadin-grid>
    </div>
  `;
}
```

**Flow** — `GridDragRowsBetweenGrids.java`

```java
Grid<Person> grid1 = setupGrid();
Grid<Person> grid2 = setupGrid();

GridListDataView<Person> dataView1 = grid1.setItems(people1);
GridListDataView<Person> dataView2 = grid2.setItems(people2);

grid1.setRowsDraggable(true);
grid1.addDragStartListener(e -> {
    draggedItem = e.getDraggedItems().get(0);
    grid1.setDropMode(GridDropMode.ON_GRID);
    grid2.setDropMode(GridDropMode.ON_GRID);
});
grid1.addDropListener(e -> {
    dataView2.removeItem(draggedItem);
    dataView1.addItem(draggedItem);
});
grid1.addDragEndListener(e -> {
    draggedItem = null;
    grid1.setDropMode(null);
    grid2.setDropMode(null);
});

grid2.setRowsDraggable(true);
grid2.addDragStartListener(e -> {
    draggedItem = e.getDraggedItems().get(0);
    grid1.setDropMode(GridDropMode.ON_GRID);
    grid2.setDropMode(GridDropMode.ON_GRID);
});
grid2.addDropListener(e -> {
    dataView1.removeItem(draggedItem);
    dataView2.addItem(draggedItem);
});
grid2.addDragEndListener(e -> {
    draggedItem = null;
    grid1.setDropMode(null);
    grid2.setDropMode(null);
});
```

**React** — `grid-drag-rows-between-grids.tsx`

```tsx
const startDraggingItem = (event: GridDragStartEvent<Person>) => {
  draggedItem.value = event.detail.draggedItems[0];
};

const clearDraggedItem = () => {
  draggedItem.value = undefined;
};

return (
  <div style={gridContainerStyle}>
    <Grid
      items={grid1Items.value}
      rowsDraggable
      dropMode={draggedItem.value ? 'on-grid' : undefined}
      style={gridStyle}
      onGridDragstart={startDraggingItem}
      onGridDragend={clearDraggedItem}
      onGridDrop={() => {
        const draggedPerson = draggedItem.value!;
        const draggedItemIndex = grid2Items.value.indexOf(draggedPerson);
        if (draggedItemIndex >= 0) {
          const updatedGrid2Items = grid2Items.value.filter(
            (_, index) => index !== draggedItemIndex
          );
          grid2Items.value = updatedGrid2Items;
          grid1Items.value = [...grid1Items.value, draggedPerson];
        }
      }}
    >
      <GridColumn header="Full name" renderer={fullNameRenderer}></GridColumn>

      <GridColumn path="profession" />
    </Grid>

    <Grid
      items={grid2Items.value}
      rowsDraggable
      dropMode={draggedItem.value ? 'on-grid' : undefined}
      style={gridStyle}
      onGridDragstart={startDraggingItem}
      onGridDragend={clearDraggedItem}
      onGridDrop={() => {
        const draggedPerson = draggedItem.value!;
        const draggedItemIndex = grid1Items.value.indexOf(draggedPerson);
        if (draggedItemIndex >= 0) {
          const updatedGrid1Items = grid1Items.value.filter(
            (_, index) => index !== draggedItemIndex
          );
          grid1Items.value = updatedGrid1Items;
          grid2Items.value = [...grid2Items.value, draggedPerson];
        }
      }}
    >
      <GridColumn header="Full name" renderer={fullNameRenderer} />

      <GridColumn path="profession" />
    </Grid>
  </div>
);
```

## <a id="drag-and-drop-filters"></a>Drag & Drop Filters

Drag-and-drop filters determine which rows are draggable and which rows are valid drop targets. These filters function on a per-row basis.

**Lit** — `tree-grid-drag-drop.ts`

```typescript
@customElement('tree-grid-drag-drop')
export class Example extends LitElement {
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    // Apply custom theme (only supported if your app uses one)
    applyTheme(root);
    return root;
  }

  @query('vaadin-grid')
  private grid!: Grid<Person>;

  @state()
  private draggedItem: Person | undefined;

  @state()
  private items: Person[] = [];

  @state()
  private managers: Person[] = [];

  @state()
  private expandedItems: Person[] = [];

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people;
    this.managers = this.items.filter((item) => item.manager);
    // Avoid using this method
    this.grid.clearCache();
  }

  private dataProvider = (
    params: GridDataProviderParams<Person>,
    callback: GridDataProviderCallback<Person>
  ) => {
    const { page, pageSize, parentItem } = params;
    const startIndex = page * pageSize;
    const endIndex = startIndex + pageSize;

    /*
    We cannot change the underlying data in this demo so this dataProvider uses
    a local field to fetch its values. This allows us to keep a reference to the
    modified list instead of loading a new list every time the dataProvider gets
    called. In a real application, you should always access your data source
    here and avoid using grid.clearCache() whenever possible.
    */
    const result = parentItem
      ? this.items.filter((item) => item.managerId === parentItem.id)
      : this.managers.slice(startIndex, endIndex);

    callback(result, result.length);
  };

  protected override render() {
    return html`
      <vaadin-grid
        .dataProvider="${this.dataProvider}"
        item-id-path="id"
        item-has-children-path="manager"
        .expandedItems="${this.expandedItems}"
        @expanded-items-changed="${(event: GridExpandedItemsChangedEvent<Person>) => {
          this.expandedItems = event.detail.value;
        }}"
        rows-draggable
        @grid-dragstart="${(event: GridDragStartEvent<Person>) => {
          this.draggedItem = event.detail.draggedItems[0];
        }}"
        @grid-dragend="${() => {
          this.draggedItem = undefined;
        }}"
        @grid-drop="${(event: GridDropEvent<Person>) => {
          const manager = event.detail.dropTargetItem;
          if (this.draggedItem) {
            // In a real application, when using a data provider, you should
            // change the persisted data instead of updating a field
            this.draggedItem.managerId = manager.id;
            // Avoid using this method
            this.grid.clearCache();
          }
        }}"
        .dropMode="${this.draggedItem ? 'on-top' : undefined}"
        .dragFilter="${(model: GridItemModel<Person>) => {
          const { item } = model;
          return !item.manager; // Only drag non-managers
        }}"
        .dropFilter="${(model: GridItemModel<Person>) => {
          const { item } = model;
          return (
            item.manager && // Can only drop on a supervisor
            item.id !== this.draggedItem?.managerId // Disallow dropping on the same manager
          );
        }}"
      >
        <vaadin-grid-tree-column path="firstName"></vaadin-grid-tree-column>
        <vaadin-grid-column path="lastName"></vaadin-grid-column>
        <vaadin-grid-column path="email"></vaadin-grid-column>
      </vaadin-grid>
    `;
  }
}
```

**Flow** — `TreeGridDragDrop.java`

```java
TreeGrid<Person> treeGrid = setupTreeGrid();
TreeData<Person> treeData = new TreeData<>();
treeData.addItems(managers, this::getStaffByManager);

// To preserve scroll position after refreshAll(), configure
// TreeDataProvider to return data in the flattened hierarchy format.
// More details can be found in the JavaDoc of the HierarchyFormat enum.
TreeDataProvider<Person> treeDataProvider = new TreeDataProvider<>(
        treeData, HierarchyFormat.FLATTENED);
treeGrid.setDataProvider(treeDataProvider);

// Enable drag and drop
treeGrid.setRowsDraggable(true);

// Set a filter to allow dragging staff only
treeGrid.setDragFilter(person -> !person.isManager());

treeGrid.addDragStartListener(e -> {
    draggedItem = e.getDraggedItems().get(0);

    // Allow dropping on top of rows only
    treeGrid.setDropMode(GridDropMode.ON_TOP);

    // Set a filter to define valid drop targets
    treeGrid.setDropFilter(person ->
            // Allow dropping on supervisors only
            person.isManager() &&
            // Disallow dropping on the same manager
            !person.getId().equals(draggedItem.getManagerId()));
});

treeGrid.addDropListener(e -> {
    Person newManager = e.getDropTargetItem().orElse(null);
    if (newManager == null) {
        return;
    }

    draggedItem.setManagerId(newManager.getId());
    treeData.setParent(draggedItem, newManager);

    // Reset TreeGrid's cache to reflect the hierarchy changes in the UI
    treeDataProvider.refreshAll();
});

treeGrid.addDragEndListener(e -> {
    draggedItem = null;

    treeGrid.setDropMode(null);
    treeGrid.setDropFilter(null);
});
```

**React** — `tree-grid-drag-drop.tsx`

```tsx
const draggedItem = useSignal<Person | undefined>(undefined);
const items = useSignal<Person[]>([]);
const expandedItems = useSignal<Person[]>([]);

useEffect(() => {
  getPeople().then(({ people }) => {
    items.value = people;
  });
}, []);

const dataProvider = useMemo(
  () => (params: GridDataProviderParams<Person>, callback: GridDataProviderCallback<Person>) => {
    const { page, pageSize, parentItem } = params;
    const startIndex = page * pageSize;
    const endIndex = startIndex + pageSize;

    /*
  We cannot change the underlying data in this demo so this dataProvider uses
  a local field to fetch its values. This allows us to keep a reference to the
  modified list instead of loading a new list every time the dataProvider gets
  called. In a real application, you should always access your data source
  here and avoid using grid.clearCache() whenever possible.
  */
    const result = parentItem
      ? items.value.filter((item) => item.managerId === parentItem.id)
      : items.value.filter((item) => item.manager).slice(startIndex, endIndex);

    callback(result, result.length);
  },
  [items.value]
);

return (
  <Grid
    dataProvider={dataProvider}
    itemIdPath="id"
    itemHasChildrenPath="manager"
    expandedItems={expandedItems.value}
    onExpandedItemsChanged={(event) => {
      expandedItems.value = event.detail.value;
    }}
    rowsDraggable
    onGridDragstart={(event) => {
      draggedItem.value = event.detail.draggedItems[0];
    }}
    onGridDragend={() => {
      draggedItem.value = undefined;
    }}
    onGridDrop={(event) => {
      const manager = event.detail.dropTargetItem;
      if (draggedItem.value) {
        draggedItem.value.managerId = manager.id;
        items.value = [...items.value];
      }
    }}
    dropMode={draggedItem.value ? 'on-top' : undefined}
    dragFilter={(model) => {
      const { item } = model;
      return !item.manager; // Only drag non-managers
    }}
    dropFilter={(model) => {
      const { item } = model;
      return (
        item.manager && // Can only drop on a supervisor
        item.id !== draggedItem.value?.managerId // Disallow dropping on the same manager
      );
    }}
  >
    <GridTreeColumn path="firstName" />
    <GridColumn path="lastName" />
    <GridColumn path="email" />
  </Grid>
);
```
