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

async dataProvider(
  params: GridDataProviderParams<Person>,
  callback: GridDataProviderCallback<Person>
) {
  // The requested page and the full length of the corresponding
  // hierarhcy 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);
}

render() {
  return html`
    <vaadin-grid .dataProvider="${this.dataProvider}">
      <vaadin-grid-tree-column
        path="firstName"
        item-has-children-path="manager"
      ></vaadin-grid-tree-column>
      <vaadin-grid-column path="lastName"></vaadin-grid-column>
      <vaadin-grid-column path="email"></vaadin-grid-column>
    </vaadin-grid>
  `;
}
Note
Features shared with Grid

Tree Grid is an extension of the Grid component and all of Grid’s features are available in Tree Grid as well.

Tree Column

The tree column is a column that 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.

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

render() {
  return html`
    <vaadin-horizontal-layout
      style="align-items: center; height: var(--lumo-size-xl);"
      theme="spacing"
    >
      <h3 style="flex-grow: 1; margin: 0;">Employee</h3>
      <vaadin-button @click="${() => (this.expandedItems = [...this.managers])}">
        Expand All
      </vaadin-button>
      <vaadin-button @click="${() => (this.expandedItems = [])}">Collapse All</vaadin-button>
    </vaadin-horizontal-layout>

    <vaadin-grid
      .dataProvider="${this.dataProvider}"
      .itemIdPath="${'id'}"
      .expandedItems="${this.expandedItems}"
    >
      <vaadin-grid-tree-column
        path="firstName"
        item-has-children-path="manager"
      ></vaadin-grid-tree-column>
      <vaadin-grid-column path="lastName"></vaadin-grid-column>
      <vaadin-grid-column path="email"></vaadin-grid-column>
    </vaadin-grid>
  `;
}

Rich Content

Like Grid, Tree Grid supports rich content.

private employeeRenderer = (
  root: HTMLElement,
  _column?: GridColumnElement,
  model?: GridItemModel<Person>
) => {
  if (model?.item) {
    const person = model.item;

    render(
      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}"
        >
          <vaadin-horizontal-layout style="align-items: center;" theme="spacing">
            <vaadin-avatar
              img="${person.pictureUrl}"
              name="${`${person.firstName} ${person.lastName}`}"
            ></vaadin-avatar>
            <vaadin-vertical-layout style="line-height: var(--lumo-line-height-m);">
              <span>${person.firstName} ${person.lastName}</span>
              <span
                style="font-size: var(--lumo-font-size-s); color: var(--lumo-secondary-text-color);"
              >
                ${person.profession}
              </span>
            </vaadin-vertical-layout>
          </vaadin-horizontal-layout>
        </vaadin-grid-tree-toggle>
      `,
      root
    );
  }
};
contactRenderer(root: HTMLElement, _column?: GridColumnElement, model?: GridItemModel<Person>) {
  if (model?.item) {
    const person = model.item;

    render(
      html`
        <vaadin-vertical-layout
          style="font-size: var(--lumo-font-size-s); line-height: var(--lumo-line-height-m);"
        >
          <a href="mailto:${person.email}" style="align-items: center; display: flex;">
            <iron-icon
              icon="vaadin:envelope"
              style="height: var(--lumo-icon-size-s); margin-inline-end: var(--lumo-space-s); width: var(--lumo-icon-size-s);"
            ></iron-icon>
            <span>${person.email}</span>
          </a>
          <a href="tel:${person.address.phone}" style="align-items: center; display: flex;">
            <iron-icon
              icon="vaadin:phone"
              style="height: var(--lumo-icon-size-s); margin-inline-end: var(--lumo-space-s); width: var(--lumo-icon-size-s);"
            ></iron-icon>
            <span>${person.address.phone}</span>
          </a>
        </vaadin-vertical-layout>
      `,
      root
    );
  }
}
render() {
  return html`
    <vaadin-grid .dataProvider="${this.dataProvider}" .expandedItems="${this.expandedItems}">
      <vaadin-grid-column
        auto-width
        header="Employee"
        .renderer="${this.employeeRenderer}"
      ></vaadin-grid-column>
      <vaadin-grid-column
        auto-width
        header="Contact"
        .renderer="${this.contactRenderer}"
      ></vaadin-grid-column>
    </vaadin-grid>
  `;
}

Best Practises

Tree Grid is not meant to be used as a navigation menu.

ComponentUsage Recommendations

Grid

Component for showing tabular data.

Grid Pro

Component for showing and editing tabular data.

CRUD

Component for creating, displaying, updating and deleting tabular data.