Virtual List
API: TypeScript / Java
Source: TypeScript / Java
Virtual List allows you to render a long list of items inside a scrollable container without sacrificing performance. Each item is rendered on the fly as the user scrolls the list.
To use the component, you need to assign it a set of data items and a renderer that’s used to render each individual data item. The height of an item is determined by its content and can change dynamically.
Open in a
new tab
new tab
Source code
virtual-list-basic.ts
import { html, LitElement, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { live } from 'lit/directives/live.js';
import '@vaadin/avatar';
import '@vaadin/details';
import type { Details } from '@vaadin/details';
import '@vaadin/horizontal-layout';
import '@vaadin/vertical-layout';
import '@vaadin/virtual-list';
import { virtualListRenderer } from '@vaadin/virtual-list/lit.js';
import type { VirtualListLitRenderer } from '@vaadin/virtual-list/lit.js';
import { getPeople } from 'Frontend/demo/domain/DataService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('virtual-list-basic')
export class Example extends LitElement {
  static override styles = css`
    vaadin-avatar {
      height: 64px;
      width: 64px;
    }
  `;
  protected override createRenderRoot() {
    const root = super.createRenderRoot();
    // Apply custom theme (only supported if your app uses one)
    applyTheme(root);
    return root;
  }
  @state()
  private people?: Person[];
  private expandedPeople = new Set<Person>();
  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.people = people;
  }
  private personCardRenderer: VirtualListLitRenderer<Person> = (person) => html`
    <vaadin-horizontal-layout theme="spacing margin">
      <vaadin-avatar
        .img="${person.pictureUrl}"
        .name="${`${person.firstName} ${person.lastName}`}"
      ></vaadin-avatar>
      <vaadin-vertical-layout>
        <b>${person.firstName} ${person.lastName}</b>
        <span>${person.profession}</span>
        <vaadin-details
          .opened="${live(this.expandedPeople.has(person))}"
          @click="${(e: Event) => {
            const details = e.currentTarget as Details;
            if (details.opened) {
              this.expandedPeople.add(person);
            } else {
              this.expandedPeople.delete(person);
            }
          }}"
        >
          <div slot="summary">Contact information</div>
          <vaadin-vertical-layout>
            <span>${person.email}</span>
            <span>${person.address.phone}</span>
          </vaadin-vertical-layout>
        </vaadin-details>
      </vaadin-vertical-layout>
    </vaadin-horizontal-layout>
  `;
  protected override render() {
    return html`
      <!-- tag::snippet[] -->
      <vaadin-virtual-list
        .items="${this.people}"
        ${virtualListRenderer(this.personCardRenderer, [])}
      ></vaadin-virtual-list>
      <!-- end::snippet[] -->
    `;
  }
}
Person.ts
import Address from './Address';
/**
 * This module is generated from com.vaadin.demo.domain.Person.
 * All changes to this file are overridden. Consider editing the corresponding Java file if necessary.
 * @see {@link file:///srv/jenkins/workspace/docs/docs-site-v23/docs/src/main/java/com/vaadin/demo/domain/Person.java}
 */
export default interface Person {
  firstName: string;
  lastName: string;
  email: string;
  birthday: string;
  id: number;
  subscriber: boolean;
  membership: string;
  pictureUrl: string;
  profession: string;
  address: Address;
  managerId?: number;
  manager: boolean;
  status: string;
}
Person.ts
VirtualListBasic.java
package com.vaadin.demo.component.virtuallist;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.avatar.Avatar;
import com.vaadin.flow.component.details.Details;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.virtuallist.VirtualList;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.dom.ElementFactory;
import com.vaadin.flow.router.Route;
import java.util.List;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
@Route("virtual-list-basic")
public class VirtualListBasic extends Div {
    private List<Person> people = DataService.getPeople();
    private ComponentRenderer<Component, Person> personCardRenderer = new ComponentRenderer<>(
            person -> {
                HorizontalLayout cardLayout = new HorizontalLayout();
                cardLayout.setMargin(true);
                Avatar avatar = new Avatar(person.getFullName(),
                        person.getPictureUrl());
                avatar.setHeight("64px");
                avatar.setWidth("64px");
                VerticalLayout infoLayout = new VerticalLayout();
                infoLayout.setSpacing(false);
                infoLayout.setPadding(false);
                infoLayout.getElement().appendChild(
                        ElementFactory.createStrong(person.getFullName()));
                infoLayout.add(new Div(new Text(person.getProfession())));
                VerticalLayout contactLayout = new VerticalLayout();
                contactLayout.setSpacing(false);
                contactLayout.setPadding(false);
                contactLayout.add(new Div(new Text(person.getEmail())));
                contactLayout
                        .add(new Div(new Text(person.getAddress().getPhone())));
                infoLayout
                        .add(new Details("Contact information", contactLayout));
                cardLayout.add(avatar, infoLayout);
                return cardLayout;
            });
    public VirtualListBasic() {
        // tag::snippet[]
        VirtualList<Person> list = new VirtualList<>();
        list.setItems(people);
        list.setRenderer(personCardRenderer);
        add(list);
        // end::snippet[]
    }
}
Person.java
package com.vaadin.demo.domain;
import java.util.Date;
import javax.annotation.Nonnull;
// tag::snippet[]
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;
    }
}
// end::snippet[]
F93C6D28-18D6-4403-B3B6-0D13F8705BED