> Markdown version of [Tree Grid](https://vaadin.com/docs/latest/components/tree-grid). Section index: [llms.txt](https://vaadin.com/docs/latest/components/llms.txt)

# Tree Grid

Tree Grid is a component for displaying hierarchical tabular data grouped into expandable nodes.

**Lit** — `tree-grid-basic.ts`

```typescript
async dataProvider(
  params: GridDataProviderParams<Person>,
  callback: GridDataProviderCallback<Person>
) {
  // The requested page and the full length of the corresponding
  // hierarchy level is requested from the data service
  const { people, hierarchyLevelSize } = await getPeople({
    count: params.pageSize,
    startIndex: params.page * params.pageSize,
    managerId: params.parentItem ? params.parentItem.id : null,
  });

  callback(people, hierarchyLevelSize);
}

protected override render() {
  return html`
    <vaadin-grid .itemHasChildrenPath="${'manager'}" .dataProvider="${this.dataProvider}">
      <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** — `TreeGridBasic.java`

```java
TreeGrid<Person> treeGrid = new TreeGrid<>();
TreeData<Person> treeData = new TreeData<>();
treeData.addItems(managers, this::getStaff);
treeGrid.setDataProvider(
        new TreeDataProvider<>(treeData, HierarchyFormat.FLATTENED));
treeGrid.addHierarchyColumn(Person::getFirstName)
        .setHeader("First name");
treeGrid.addColumn(Person::getLastName).setHeader("Last name");
treeGrid.addColumn(Person::getEmail).setHeader("Email");
```

**Flow** — `Person.java`

```java
public class Person {

    @Nonnull
    private String firstName;

    @Nonnull
    private String lastName;

    @Nonnull
    private String email;

    @Nonnull
    private Date birthday;

    @Nonnull
    private Integer id;

    @Nonnull
    private Boolean subscriber;

    @Nonnull
    private String membership;

    @Nonnull
    private String pictureUrl;

    @Nonnull
    private String profession;

    @Nonnull
    private Address address;

    private Integer managerId;

    @Nonnull
    private Boolean manager;

    @Nonnull
    private String status;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public boolean isSubscriber() {
        return subscriber;
    }

    public void setSubscriber(boolean subscriber) {
        this.subscriber = subscriber;
    }

    public String getMembership() {
        return membership;
    }

    public void setMembership(String membership) {
        this.membership = membership;
    }

    public String getPictureUrl() {
        return pictureUrl;
    }

    public void setPictureUrl(String pictureUrl) {
        this.pictureUrl = pictureUrl;
    }

    public String getProfession() {
        return profession;
    }

    public void setProfession(String profession) {
        this.profession = profession;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return id == other.id;
    }

    public Integer getManagerId() {
        return managerId;
    }

    public void setManagerId(Integer managerId) {
        this.managerId = managerId;
    }

    public boolean isManager() {
        return manager;
    }

    public void setManager(boolean manager) {
        this.manager = manager;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
```

**React** — `tree-grid-basic.tsx`

```tsx
async function dataProvider(
  params: GridDataProviderParams<Person>,
  callback: GridDataProviderCallback<Person>
) {
  // The requested page and the full length of the corresponding
  // hierarchy level is requested from the data service
  const { people, hierarchyLevelSize } = await getPeople({
    count: params.pageSize,
    startIndex: params.page * params.pageSize,
    managerId: params.parentItem ? params.parentItem.id : null,
  });

  callback(people, hierarchyLevelSize);
}

function Example() {
  return (
    <Grid itemHasChildrenPath="manager" dataProvider={dataProvider}>
      <GridTreeColumn path="firstName" />
      <GridColumn path="lastName" />
      <GridColumn path="email" />
    </Grid>
  );
}
```

Tree Grid is an extension of the Grid component, so it inherits all of Grid’s features, including:

- [Sorting](https://vaadin.com/docs/latest/components/grid.md#sorting)

- [Filtering](https://vaadin.com/docs/latest/components/grid.md#filtering)

- [Columns](https://vaadin.com/docs/latest/components/grid/columns.md)

- [Selection](https://vaadin.com/docs/latest/components/grid/selection.md)

- [Renderers](https://vaadin.com/docs/latest/components/grid/renderers.md)

- and [more](https://vaadin.com/docs/latest/components/grid.md)

However, Tree Grid isn’t meant to be used as a navigation menu.

## <a id="tree-column"></a>Tree Column

The tree column contains the toggles for expanding and collapsing nodes. Nodes are opened and closed by clicking a tree column’s cell. They can also be toggled programmatically.

**Lit** — `tree-grid-column.ts`

```typescript
@state()
private expandedItems: unknown[] = [];

protected override render() {
  return html`
    <vaadin-horizontal-layout style="align-items: center; height: 3.5rem;" theme="spacing">
      <h3>Employee</h3>
      <vaadin-button slot="end" @click="${this.expandAll}">Expand All</vaadin-button>
      <vaadin-button slot="end" @click="${this.collapseAll}">Collapse All</vaadin-button>
    </vaadin-horizontal-layout>

    <vaadin-grid
      .dataProvider="${this.dataProvider}"
      .itemIdPath="${'id'}"
      .itemHasChildrenPath="${'manager'}"
      .expandedItems="${this.expandedItems}"
    >
      <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>
  `;
}

private expandAll() {
  this.expandedItems = [...this.managers];
}

private collapseAll() {
  this.expandedItems = [];
}
```

**Flow** — `TreeGridColumn.java`

```java
Button expand = new Button("Expand All");
expand.addClickListener(event -> treeGrid.expand(managers));

Button collapse = new Button("Collapse All");
collapse.addClickListener(event -> treeGrid.collapse(managers));
```

**Flow** — `Person.java`

```java
public class Person {

    @Nonnull
    private String firstName;

    @Nonnull
    private String lastName;

    @Nonnull
    private String email;

    @Nonnull
    private Date birthday;

    @Nonnull
    private Integer id;

    @Nonnull
    private Boolean subscriber;

    @Nonnull
    private String membership;

    @Nonnull
    private String pictureUrl;

    @Nonnull
    private String profession;

    @Nonnull
    private Address address;

    private Integer managerId;

    @Nonnull
    private Boolean manager;

    @Nonnull
    private String status;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public boolean isSubscriber() {
        return subscriber;
    }

    public void setSubscriber(boolean subscriber) {
        this.subscriber = subscriber;
    }

    public String getMembership() {
        return membership;
    }

    public void setMembership(String membership) {
        this.membership = membership;
    }

    public String getPictureUrl() {
        return pictureUrl;
    }

    public void setPictureUrl(String pictureUrl) {
        this.pictureUrl = pictureUrl;
    }

    public String getProfession() {
        return profession;
    }

    public void setProfession(String profession) {
        this.profession = profession;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return id == other.id;
    }

    public Integer getManagerId() {
        return managerId;
    }

    public void setManagerId(Integer managerId) {
        this.managerId = managerId;
    }

    public boolean isManager() {
        return manager;
    }

    public void setManager(boolean manager) {
        this.manager = manager;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
```

**React** — `tree-grid-column.tsx`

```tsx
<HorizontalLayout style={{ alignItems: 'center', height: '3.5rem' }} theme="spacing">
  <h3>Employee</h3>
  <Button slot="end" onClick={expandAll}>
    Expand All
  </Button>
  <Button slot="end" onClick={collapseAll}>
    Collapse All
  </Button>
</HorizontalLayout>

<Grid
  dataProvider={dataProvider}
  itemIdPath="id"
  itemHasChildrenPath="manager"
  expandedItems={expandedItems.value}
>
  <GridTreeColumn path="firstName" />
  <GridColumn path="lastName" />
  <GridColumn path="email" />
</Grid>
```

## <a id="rich-content"></a>Rich Content

Like Grid, Tree Grid supports rich content.

**Lit** — `tree-grid-rich-content.ts`

```typescript
private employeeRenderer: GridColumnBodyLitRenderer<Person> = (person, model) => html`
  <vaadin-grid-tree-toggle
    .leaf="${!person.manager}"
    .level="${model.level ?? 0}"
    @expanded-changed="${(e: GridTreeToggleExpandedChangedEvent) => {
      if (e.detail.value) {
        this.expandedItems = [...this.expandedItems, person];
      } else {
        this.expandedItems = this.expandedItems.filter((p) => p.id !== person.id);
      }
    }}"
    .expanded="${!!model.expanded}"
  >
    <div class="person-item">
      <vaadin-avatar
        .img="${person.pictureUrl}"
        .name="${`${person.firstName} ${person.lastName}`}"
        style="--vaadin-avatar-size: 2.25rem"
      ></vaadin-avatar>
      <span>${person.firstName} ${person.lastName}</span>
      <span>${person.profession}</span>
    </div>
  </vaadin-grid-tree-toggle>
`;

private contactRenderer: GridColumnBodyLitRenderer<Person> = (person) => html`
  <vaadin-vertical-layout style="font-size: .875rem; line-height: 1.625;">
    <a href="mailto:${person.email}" style="align-items: center; display: flex;">
      <vaadin-icon
        icon="vaadin:envelope"
        style="width: 1.25em; height: 1.25em; margin-inline-end: var(--vaadin-gap-s);"
      ></vaadin-icon>
      <span>${person.email}</span>
    </a>
    <a href="tel:${person.address.phone}" style="align-items: center; display: flex;">
      <vaadin-icon
        icon="vaadin:phone"
        style="width: 1.25em; height: 1.25em; margin-inline-end: var(--vaadin-gap-s);"
      ></vaadin-icon>
      <span>${person.address.phone}</span>
    </a>
  </vaadin-vertical-layout>
`;

protected override render() {
  return html`
    <vaadin-grid .dataProvider="${this.dataProvider}" .expandedItems="${this.expandedItems}">
      <vaadin-grid-column
        auto-width
        header="Employee"
        ${columnBodyRenderer(this.employeeRenderer, [])}
      ></vaadin-grid-column>
      <vaadin-grid-column
        auto-width
        header="Contact"
        ${columnBodyRenderer(this.contactRenderer, [])}
      ></vaadin-grid-column>
    </vaadin-grid>
  `;
}
```

**Flow** — `TreeGridRichContent.java`

```java
treeGrid.addComponentHierarchyColumn(person -> {
    Avatar avatar = new Avatar(person.getFullName(),
            person.getPictureUrl());
    avatar.getStyle().set("--vaadin-avatar-size", "2.25rem");
    Span name = new Span(person.getFullName());
    Span profession = new Span(person.getProfession());

    Div personItem = new Div(avatar, name, profession);
    personItem.addClassName("person-item");
    return personItem;
}).setHeader("Employee");

treeGrid.addComponentColumn(person -> {
    Icon emailIcon = createIcon(VaadinIcon.ENVELOPE);
    Span email = new Span(person.getEmail());

    Anchor emailLink = new Anchor();
    emailLink.add(emailIcon, email);
    emailLink.setHref("mailto:" + person.getEmail());
    emailLink.getStyle().set("align-items", "center").set("display",
            "flex");

    Icon phoneIcon = createIcon(VaadinIcon.PHONE);
    Span phone = new Span(person.getAddress().getPhone());

    Anchor phoneLink = new Anchor();
    phoneLink.add(phoneIcon, phone);
    phoneLink.setHref("tel:" + person.getAddress().getPhone());
    phoneLink.getStyle().set("align-items", "center").set("display",
            "flex");

    VerticalLayout column = new VerticalLayout(emailLink, phoneLink);
    column.getStyle().set("font-size", "0.875rem").set("line-height",
            "1.625");
    column.setPadding(false);
    column.setSpacing(false);
    return column;
}).setHeader("Contact");
```

**Flow** — `Person.java`

```java
public class Person {

    @Nonnull
    private String firstName;

    @Nonnull
    private String lastName;

    @Nonnull
    private String email;

    @Nonnull
    private Date birthday;

    @Nonnull
    private Integer id;

    @Nonnull
    private Boolean subscriber;

    @Nonnull
    private String membership;

    @Nonnull
    private String pictureUrl;

    @Nonnull
    private String profession;

    @Nonnull
    private Address address;

    private Integer managerId;

    @Nonnull
    private Boolean manager;

    @Nonnull
    private String status;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public boolean isSubscriber() {
        return subscriber;
    }

    public void setSubscriber(boolean subscriber) {
        this.subscriber = subscriber;
    }

    public String getMembership() {
        return membership;
    }

    public void setMembership(String membership) {
        this.membership = membership;
    }

    public String getPictureUrl() {
        return pictureUrl;
    }

    public void setPictureUrl(String pictureUrl) {
        this.pictureUrl = pictureUrl;
    }

    public String getProfession() {
        return profession;
    }

    public void setProfession(String profession) {
        this.profession = profession;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Person)) {
            return false;
        }
        Person other = (Person) obj;
        return id == other.id;
    }

    public Integer getManagerId() {
        return managerId;
    }

    public void setManagerId(Integer managerId) {
        this.managerId = managerId;
    }

    public boolean isManager() {
        return manager;
    }

    public void setManager(boolean manager) {
        this.manager = manager;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
```

**React** — `tree-grid-rich-content.tsx`

```tsx
function contactRenderer({ item: person }: { item: Person }) {
  return (
    <VerticalLayout
      style={{
        fontSize: '.875rem',
        lineHeight: '1.625',
      }}
    >
      <a href={`mailto:${person.email}`} style={{ display: 'flex', alignItems: 'center' }}>
        <Icon
          icon="vaadin:envelope"
          style={{
            width: '1.25em',
            height: '1.25em',
            marginInlineEnd: 'var(--vaadin-gap-s)',
          }}
        />
        <span>{person.email}</span>
      </a>
      <a href={`tel:${person.address.phone}`} style={{ display: 'flex', alignItems: 'center' }}>
        <Icon
          icon="vaadin:phone"
          style={{
            width: '1.25em',
            height: '1.25em',
            marginInlineEnd: 'var(--vaadin-gap-s)',
          }}
        />
        <span>{person.address.phone}</span>
      </a>
    </VerticalLayout>
  );
}

function Example() {
  const expandedItems = useSignal<Person[]>([]);

  const toggleRenderer = useCallback(
    ({ item: person, model }: { item: Person; model: { level?: number; expanded?: boolean } }) => (
      <GridTreeToggle
        leaf={!person.manager}
        level={model?.level ?? 0}
        expanded={!!model?.expanded}
        onClick={(e) => {
          if (!e.defaultPrevented) {
            return;
          }
          if (e.currentTarget.expanded) {
            expandedItems.value = [...expandedItems.value, person];
          } else {
            expandedItems.value = expandedItems.value.filter((p) => p.id !== person.id);
          }
        }}
      >
        <div className="person-item">
          <Avatar
            img={person.pictureUrl}
            name={`${person.firstName} ${person.lastName}`}
            style={{ '--vaadin-avatar-size': '2.25rem' }}
          />
          <span>
            {person.firstName} {person.lastName}
          </span>
          <span>{person.profession}</span>
        </div>
      </GridTreeToggle>
    ),
    []
  );

  return (
    <Grid dataProvider={dataProvider} expandedItems={expandedItems.value}>
      <GridColumn autoWidth header="Employee" renderer={toggleRenderer} />
      <GridColumn autoWidth header="Contact" renderer={contactRenderer} />
    </Grid>
  );
}
```

`person-item.css`

```css
.person-item {
    display: grid;
    grid-template-columns: min-content auto;
    grid-template-rows: auto auto;
    grid-template-areas:
        "avatar name"
        "avatar title";
    gap: 0 var(--vaadin-gap-s);
    align-items: center;
    line-height: 1.2;

    & > :is(vaadin-avatar, img) {
        grid-area: avatar;
    }

    & > span:first-of-type {
        grid-area: name;
    }

    & > span:last-of-type {
        grid-area: title;
        font-size: 0.875rem;
        font-weight: 400;
        color: var(--vaadin-text-color-secondary);
    }
}
```

## <a id="programmatic-scrolling"></a>Programmatic Scrolling

Tree Grid supports programmatic navigation to a specific item. This is particularly useful when dealing with large data sets. It saves users from having to scroll through potentially hundreds or thousands of rows.

Tree Grid also supports scrolling to a column, which works the same as in the Grid component. See the [Grid Programmatic Scrolling](https://vaadin.com/docs/latest/components/grid.md#programmatic-scrolling) documentation.

### <a id="scrolling-to-item-by-reference-flow"></a>Scrolling to Item By Reference Flow

In Flow, you can bring an item into view by passing its object reference to the `scrollToItem(T item)` method. This method expands all parent items leading to the specified item and then scrolls the Tree Grid to position that item at the top of the viewport. Scrolling is skipped if the item is already in the viewport and fully visible.

`TreeGridScrollToItem.java`

```java
treeGrid.scrollToItem(item);
```

This feature is available out of the box when using [`TreeDataProvider`](https://vaadin.com/docs/latest/components/tree-grid/data-binding.md#tree-data-provider), a built-in data provider for connecting a [`TreeData`](https://vaadin.com/docs/latest/components/tree-grid/data-binding.md#tree-data-provider) structure to the Tree Grid.

Other data providers must implement two additional methods to support this feature:

- `HierarchicalDataProvider#getParent(T item)`

- `HierarchicalDataProvider#getItemIndex(T item, HierarchicalQuery query)`

You can find more information about using custom hierarchical data providers on the [Data Binding](https://vaadin.com/docs/latest/components/tree-grid/data-binding.md#custom-data-providers) page.

### <a id="scrolling-to-item-by-path"></a>Scrolling to Item by Path

You can also scroll to an item by providing its hierarchical path – an array of indexes where each index refers to a child of the item at the previous index. For example, to scroll to the second child-row (index 1) of the third root-level row (index 2), you would provide the path 2, 1. Scrolling continues until it reaches the last index in the array or encounters a collapsed item.

**Lit** — `tree-grid-scroll-to-index.ts`

```typescript
this.grid.scrollToIndex(...this.indexesToScrollTo);
```

**Flow** — `TreeGridScrollToIndex.java`

```java
treeGrid.scrollToIndex(indexesToScrollTo);
```

**React** — `tree-grid-scroll-to-index.tsx`

```tsx
grid.scrollToIndex(...indexesToScrollTo.value);
```

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

Tree Grid supports drag-and-drop operations. You can enable and handle them in your logic, for example, to allow users to move rows from one parent node to another:

**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>
);
```

For more drag-and-drop examples and API references, refer to the Grid’s [Drag and Drop](https://vaadin.com/docs/latest/components/grid/drag-drop.md) documentation.

> **Note:** In Flow, Tree Grid drag-and-drop works best with data providers that use the flattened hierarchy format. This format gives more predictable scrolling and allows Tree Grid to maintain its scroll position after hierarchy changes. For more details on this and other supported formats, see the [Hierarchy Formats](https://vaadin.com/docs/latest/components/tree-grid/data-binding.md#hierarchy-formats) section on the [Data Binding](https://vaadin.com/docs/latest/components/tree-grid/data-binding.md) page.

## <a id="related-components"></a>Related Components

| Component                                                         | Usage Recommendation                                                     |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------ |
| [Grid](https://vaadin.com/docs/latest/components/grid.md)         | Component for showing tabular data.                                      |
| [Grid Pro](https://vaadin.com/docs/latest/components/grid-pro.md) | Component for showing and editing tabular data.                          |
| [CRUD](https://vaadin.com/docs/latest/components/crud.md)         | Component for creating, displaying, updating, and deleting tabular data. |

`9DEF736F-975A-469A-8625-A0A6E086BF50`
