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

# Grid

Grid is a component for showing tabular data.

**TypeScript** — `grid-basic.ts`

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

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

protected override render() {
  return html`
    <vaadin-grid .items="${this.items}">
      <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-column path="profession"></vaadin-grid-column>
    </vaadin-grid>
  `;
}
```

**Java** — `GridBasic.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");
grid.addColumn(Person::getProfession).setHeader("Profession");

List<Person> people = DataService.getPeople();
grid.setItems(people);
```

**Java** — `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;
    }
}
```

## <a id="content"></a>Content

A basic Grid uses plain text to display information in rows and columns. Rich content can be used to provide additional information in a more legible fashion. Components such as [input fields](https://vaadin.com/docs/v23/components/input-fields.md) and [Button](https://vaadin.com/docs/v23/components/button.md) are also supported.

**TypeScript** — `grid-content.ts`

```typescript
@state()
private items?: Person[];

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

protected override render() {
  return html`
    <vaadin-grid .items="${this.items}">
      <vaadin-grid-selection-column></vaadin-grid-selection-column>
      <vaadin-grid-column
        header="Employee"
        flex-grow="0"
        auto-width
        ${columnBodyRenderer(this.employeeRenderer, [])}
      ></vaadin-grid-column>
      <vaadin-grid-column path="profession" auto-width></vaadin-grid-column>
      <vaadin-grid-column
        header="Status"
        auto-width
        ${columnBodyRenderer(this.statusRenderer, [])}
      ></vaadin-grid-column>
    </vaadin-grid>
  `;
}

private employeeRenderer: GridColumnBodyLitRenderer<Person> = (person) => html`
  <vaadin-horizontal-layout style="align-items: center;" theme="spacing">
    <vaadin-avatar
      img="${person.pictureUrl}"
      name="${person.firstName} ${person.lastName}"
      alt="User avatar"
    ></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.email}
      </span>
    </vaadin-vertical-layout>
  </vaadin-horizontal-layout>
`;

private statusRenderer: GridColumnBodyLitRenderer<Person> = ({ status }) => html`
  <span theme="badge ${status === 'Available' ? 'success' : 'error'}">${status}</span>
`;
```

**Java** — `GridContent.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.setSelectionMode(Grid.SelectionMode.MULTI);
grid.addColumn(createEmployeeRenderer()).setHeader("Employee")
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(Person::getProfession).setHeader("Profession")
        .setAutoWidth(true);
grid.addColumn(createStatusComponentRenderer()).setHeader("Status")
        .setAutoWidth(true);

...

private static Renderer<Person> createEmployeeRenderer() {
    return LitRenderer.<Person> of(
            "<vaadin-horizontal-layout style=\"align-items: center;\" theme=\"spacing\">"
                    + "<vaadin-avatar img=\"${item.pictureUrl}\" name=\"${item.fullName}\" alt=\"User avatar\"></vaadin-avatar>"
                    + "  <vaadin-vertical-layout style=\"line-height: var(--lumo-line-height-m);\">"
                    + "    <span> ${item.fullName} </span>"
                    + "    <span style=\"font-size: var(--lumo-font-size-s); color: var(--lumo-secondary-text-color);\">"
                    + "      ${item.email}" + "    </span>"
                    + "  </vaadin-vertical-layout>"
                    + "</vaadin-horizontal-layout>")
            .withProperty("pictureUrl", Person::getPictureUrl)
            .withProperty("fullName", Person::getFullName)
            .withProperty("email", Person::getEmail);
}

private static final SerializableBiConsumer<Span, Person> statusComponentUpdater = (
        span, person) -> {
    boolean isAvailable = "Available".equals(person.getStatus());
    String theme = String.format("badge %s",
            isAvailable ? "success" : "error");
    span.getElement().setAttribute("theme", theme);
    span.setText(person.getStatus());
};

private static ComponentRenderer<Span, Person> createStatusComponentRenderer() {
    return new ComponentRenderer<>(Span::new, statusComponentUpdater);
}
```

### <a id="component-renderer-vs-lit-renderer-flow-only"></a>Component Renderer vs. Lit Renderer (Flow Only)

As demonstrated in the previous example, custom content can be rendered using component renderers or Lit renderers.

#### <a id="component-renderer"></a>Component Renderer

Component renderers are easy to build, but slow to render. For a given column, they generate a component for each item in the dataset. The rendered components are fully controllable on the server side.

For each rendered cell, Grid creates a corresponding component instance on the server side. A dataset of 100 items and 10 columns using component renderer produce up to 1,000 components that need to be managed. The more components you use in a component renderer, the greater the impact on performance.

Component renderers are very flexible and easy to use, but should be used with caution. They’re better suited as editors since only a single row can be edited at a time. They can also be used for detail rows.

#### <a id="lit-renderer"></a>Lit Renderer

Lit renderers will render quickly, but require you to write HTML. To use components with Lit renderers, you need to use their HTML format. Lit templates are immutable, meaning the state of the components can’t be managed on the server side. However, the template can have different representations, depending on the state of the item.

The only data sent from the server, other than the template itself — which is sent only once — is the extra name property of each item.

Lit templates still enable event handling on the server side. However, you can’t, for example, disable or change the text of a button from the event handler. For such situations, use editors instead.

With Lit renderers, the server doesn’t keep track of the components in each cell. It only manages the state of the item in each row. The client side doesn’t have to wait for the server to send missing information about what needs to be rendered. It can use the template to render all of the cells it needs.

For more in-depth information, see [Using Lit Renderers with Grid](https://vaadin.com/docs/v23/components/grid/flow.md#using-lit-renderers).

### <a id="wrap-cell-content"></a>Wrap Cell Content

Overflowing cell content is clipped or truncated by default. This variant makes the content wrap instead.

**TypeScript** — `grid-wrap-cell-content.ts`

```typescript
@customElement('grid-wrap-cell-content')
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;
  }

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

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

  protected override render() {
    return html`
      <vaadin-grid .items="${this.items}" theme="wrap-cell-content">
        <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
          header="Address"
          ${columnBodyRenderer(this.addressRenderer, [])}
        ></vaadin-grid-column>
      </vaadin-grid>
    `;
  }

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

  private addressRenderer: GridColumnBodyLitRenderer<Person> = ({ address }) => html`
    <span>${address.street} ${address.city} ${address.zip} ${address.state}</span>
  `;
}
```

**Java** — `GridWrapCellContent.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(createAvatarRenderer()).setHeader("Image")
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(GridWrapCellContent::formatAddress).setHeader("Address");

grid.addThemeVariants(GridVariant.LUMO_WRAP_CELL_CONTENT);
```

[Tooltips](#tooltips) can also be used to display content that doesn’t fit into the cell.

## <a id="dynamic-height"></a>Dynamic Height

Grid has a default height of 400 pixels. It becomes scrollable when its items overflow the allocated space.

In addition to setting any fixed or relative value, the height of a grid can be set by the number of items in the dataset. The grid grows and shrinks based on the row count. This disables scrolling and shouldn’t be used for large data sets to avoid performance issues.

**TypeScript** — `grid-dynamic-height.ts`

```typescript
<vaadin-grid .items="${this.invitedPeople}" all-rows-visible>
  <vaadin-grid-column header="Name" path="displayName" auto-width></vaadin-grid-column>
  <vaadin-grid-column path="email"></vaadin-grid-column>
  <vaadin-grid-column path="address.phone"></vaadin-grid-column>
  <vaadin-grid-column
    header="Manage"
    ${columnBodyRenderer(this.manageRenderer, [])}
  ></vaadin-grid-column>
</vaadin-grid>
```

**Java** — `GridDynamicHeight.java`

```java
grid = new Grid<>(Person.class, false);
grid.setAllRowsVisible(true);
```

## <a id="selection"></a>Selection

Selection isn’t enabled by default. Grid supports single- and multi-select. Single-select allows the user to select only one item, while multi-select permits multiple items to be selected.

### <a id="single-selection-mode"></a>Single-Selection Mode

In single-selection mode, the user can select and deselect rows by clicking anywhere on the row.

**TypeScript** — `grid-single-selection-mode.ts`

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

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

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

protected override render() {
  return html`
    <vaadin-grid
      .items="${this.items}"
      .selectedItems="${this.selectedItems}"
      @active-item-changed="${(e: GridActiveItemChangedEvent<Person>) => {
        const item = e.detail.value;
        this.selectedItems = item ? [item] : [];
      }}"
    >
      <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>
  `;
}
```

**Java** — `GridSingleSelectionMode.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");

List<Person> people = DataService.getPeople();
grid.setItems(people);

grid.addSelectionListener(selection -> {
    Optional<Person> optionalPerson = selection.getFirstSelectedItem();
    if (optionalPerson.isPresent()) {
        // System.out.printf("Selected person: %s%n",
        // optionalPerson.get().getFullName());
    }
});
```

### <a id="multi-select-mode"></a>Multi-Select Mode

In multi-select mode, the user can use a checkbox column to select and deselect rows.

**TypeScript** — `grid-multi-select-mode.ts`

```typescript
@customElement('grid-multi-select-mode')
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;
  }

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

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

  protected override render() {
    return html`
      <vaadin-grid .items="${this.items}">
        <vaadin-grid-selection-column></vaadin-grid-selection-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>
    `;
  }
}
```

**Java** — `GridMultiSelectionMode.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.setSelectionMode(Grid.SelectionMode.MULTI);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");

List<Person> people = DataService.getPeople();
grid.setItems(people);

grid.addSelectionListener(selection -> {
    // System.out.printf("Number of selected people: %s%n",
    // selection.getAllSelectedItems().size());
});
```

## <a id="columns"></a>Columns

Column alignment, freezing (i.e., fixed positioning), grouping, headers and footers, visibility, and width can be configured. Users can be permitted to resize and reorder columns.

### <a id="column-alignment"></a>Column Alignment

Three different column alignments are supported: left (default), center, and right.

Right alignment is useful when comparing numeric values, as it helps with readability and scannability. Tabular numbers — if the font offers them — or a monospace font could be used to further improve digit alignment.

**TypeScript** — `grid-column-alignment.ts`

```typescript
<vaadin-grid .items="${this.items}">
  <vaadin-grid-column path="displayName" header="Name"></vaadin-grid-column>
  <vaadin-grid-column
    header="Due"
    ${columnBodyRenderer(() => html`<span>${this.randomDate()}</span>`, [])}
  ></vaadin-grid-column>
  <vaadin-grid-column
    header="Amount"
    text-align="end"
    ${columnBodyRenderer(
      () => html`
        <span style="font-variant-numeric: tabular-nums">${this.randomAmount()}</span>
      `,
      []
    )}
  ></vaadin-grid-column>
</vaadin-grid>
```

**Java** — `GridColumnAlignment.java`

```java
grid.addColumn(GridColumnAlignment::generateRandomAmountText)
        .setHeader("Amount").setTextAlign(ColumnTextAlign.END);
```

### <a id="column-freezing"></a>Column Freezing

Columns and column groups can be frozen — made *sticky* — to exclude them from scrolling a grid, horizontally. This can be useful for keeping the most important columns always visible in a grid with many columns. Freezing columns at the end of the grid (since V23.1) is useful, for example, for keeping row actions always visible.

**TypeScript** — `grid-column-freezing.ts`

```typescript
<vaadin-grid-column
  frozen
  header="Name"
  auto-width
  flex-grow="0"
  ${columnBodyRenderer<Person>(
    (person) => html`${person.firstName} ${person.lastName}`,
    []
  )}
></vaadin-grid-column>
<vaadin-grid-column
  frozen-to-end
  auto-width
  flex-grow="0"
  ${columnBodyRenderer(
    () => html`<vaadin-button theme="tertiary-inline">Edit</vaadin-button>`,
    []
  )}
></vaadin-grid-column>
```

**Java** — `GridColumnFreezing.java`

```java
grid.addColumn(createPersonRenderer()).setHeader("Name").setFrozen(true)
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(createActionRenderer()).setFrozenToEnd(true)
        .setAutoWidth(true).setFlexGrow(0);
```

Although it’s technically possible to freeze any column, it should be used primarily to freeze columns at the start or end of the grid, leaving the remaining columns unfrozen.

### <a id="column-grouping"></a>Column Grouping

It’s possible to group columns together. Grouped columns share a common header and footer. Use this feature to better visualize and organize related or hierarchical data.

**TypeScript** — `grid-column-grouping.ts`

```typescript
@customElement('grid-column-grouping')
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;
  }

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

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

  protected override render() {
    return html`
      <vaadin-grid .items="${this.items}">
        <vaadin-grid-column-group header="Name">
          <vaadin-grid-column path="firstName"></vaadin-grid-column>
          <vaadin-grid-column path="lastName"></vaadin-grid-column>
        </vaadin-grid-column-group>
        <vaadin-grid-column-group header="Address">
          <vaadin-grid-column path="address.street"></vaadin-grid-column>
          <vaadin-grid-column path="address.city"></vaadin-grid-column>
          <vaadin-grid-column path="address.zip"></vaadin-grid-column>
          <vaadin-grid-column path="address.state"></vaadin-grid-column>
        </vaadin-grid-column-group>
      </vaadin-grid>
    `;
  }
}
```

**Java** — `GridColumnGrouping.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name");
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name");
Grid.Column<Person> streetColumn = grid
        .addColumn(person -> person.getAddress().getStreet())
        .setHeader("Street");
Grid.Column<Person> cityColumn = grid
        .addColumn(person -> person.getAddress().getCity())
        .setHeader("City");
Grid.Column<Person> zipColumn = grid
        .addColumn(person -> person.getAddress().getZip())
        .setHeader("Zip");
Grid.Column<Person> stateColumn = grid
        .addColumn(person -> person.getAddress().getState())
        .setHeader("State");

HeaderRow headerRow = grid.prependHeaderRow();
headerRow.join(firstNameColumn, lastNameColumn).setText("Name");
headerRow.join(streetColumn, cityColumn, zipColumn, stateColumn)
        .setText("Address");
```

### <a id="column-headers-footers"></a>Column Headers & Footers

Each column has its own customizable header and footer. A basic column header shows the name in plain text. Footers are empty and thus hidden by default. Both can contain rich content and components.

**TypeScript** — `grid-column-header-footer.ts`

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

protected override async firstUpdated() {
  const { people } = await getPeople();
  this.items = people.map((person) => ({
    ...person,
    displayName: `${person.firstName} ${person.lastName}`,
  }));
}

protected override render() {
  return html`
    <vaadin-grid .items="${this.items}">
      <vaadin-grid-column
        header="Name"
        path="displayName"
        ${columnFooterRenderer(() => html`<span>200 total members</span>`, [])}
      ></vaadin-grid-column>
      <vaadin-grid-column
        ${columnHeaderRenderer(this.subscriberHeaderRenderer, [])}
        ${columnBodyRenderer(this.subscriberRenderer, [])}
        ${columnFooterRenderer(() => html`<span>102 subscribers</span>`, [])}
      ></vaadin-grid-column>
      <vaadin-grid-column
        path="membership"
        ${columnHeaderRenderer(this.membershipHeaderRenderer, [])}
        ${columnFooterRenderer(() => html`<span>103 regular, 71 premium , 66 VIP</span>`, [])}
      ></vaadin-grid-column>
    </vaadin-grid>
  `;
}

private subscriberHeaderRenderer = () => html`
  <vaadin-horizontal-layout style="align-items: center;">
    <span>Subscriber</span>
    <vaadin-icon
      icon="vaadin:info-circle"
      title="Subscribers are paying customers"
      style="height: var(--lumo-font-size-m); color: var(--lumo-contrast-70pct);"
    ></vaadin-icon>
  </vaadin-horizontal-layout>
`;

private subscriberRenderer: GridColumnBodyLitRenderer<Person> = (person) =>
  html`<span>${person.subscriber ? 'Yes' : 'No'}</span>`;

private membershipHeaderRenderer = () => html`
  <vaadin-horizontal-layout style="align-items: center;">
    <span>Membership</span>
    <vaadin-icon
      icon="vaadin:info-circle"
      title="Membership levels determines which features a client has access to"
      style="height: var(--lumo-font-size-m); color: var(--lumo-contrast-70pct);"
    ></vaadin-icon>
  </vaadin-horizontal-layout>
`;
```

**Java** — `GridColumnHeaderFooter.java`

```java
List<Person> people = DataService.getPeople();

Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFullName).setHeader("Name")
        .setFooter(String.format("%s total members", people.size()));
grid.addColumn(person -> person.isSubscriber() ? "Yes" : "No")
        .setHeader(createSubscriberHeader())
        .setFooter(createSubscriberFooterText(people));
grid.addColumn(Person::getMembership)
        .setHeader(createMembershipHeader())
        .setFooter(createMembershipFooterText(people));

...

private static Component createSubscriberHeader() {
    Span span = new Span("Subscriber");
    Icon icon = VaadinIcon.INFO_CIRCLE.create();
    icon.getElement().setAttribute("title",
            "Subscribers are paying customers");
    icon.getStyle().set("height", "var(--lumo-font-size-m)").set("color",
            "var(--lumo-contrast-70pct)");

    HorizontalLayout layout = new HorizontalLayout(span, icon);
    layout.setAlignItems(FlexComponent.Alignment.CENTER);
    layout.setSpacing(false);

    return layout;
}

private static String createSubscriberFooterText(List<Person> people) {
    long subscriberCount = people.stream().filter(Person::isSubscriber)
            .count();

    return String.format("%s subscribers", subscriberCount);
}

private static Component createMembershipHeader() {
    Span span = new Span("Membership");
    Icon icon = VaadinIcon.INFO_CIRCLE.create();
    icon.getElement().setAttribute("title",
            "Membership levels determines which features a client has access to");
    icon.getStyle().set("height", "var(--lumo-font-size-m)").set("color",
            "var(--lumo-contrast-70pct)");

    HorizontalLayout layout = new HorizontalLayout(span, icon);
    layout.setAlignItems(FlexComponent.Alignment.CENTER);
    layout.setSpacing(false);

    return layout;
}

private static String createMembershipFooterText(List<Person> people) {
    long regularCount = people.stream()
            .filter(person -> "Regular".equals(person.getMembership()))
            .count();
    long premiumCount = people.stream()
            .filter(person -> "Premium".equals(person.getMembership()))
            .count();
    long vipCount = people.stream()
            .filter(person -> "VIP".equals(person.getMembership())).count();

    return String.format("%s regular, %s premium, %s VIP", regularCount,
            premiumCount, vipCount);
}
```

### <a id="column-visibility"></a>Column Visibility

Columns and column groups can be hidden. You can provide the user with a menu for toggling column visibilities, for example using Menu Bar.

Allowing the user to hide columns is useful when only a subset of the columns is relevant to their task, and if there are plenty of columns.

**TypeScript** — `grid-column-visibility.ts`

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

@state()
private contextMenuItems: Array<ContextMenuItem & { key: string }> = [
  { text: 'First name', checked: true, key: 'firstName' },
  { text: 'Last name', checked: true, key: 'lastName' },
  { text: 'Email', checked: true, key: 'email' },
  { text: 'Phone', checked: true, key: 'phone' },
  { text: 'Profession', checked: true, key: 'profession' },
];

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

protected override render() {
  return html`
    <vaadin-horizontal-layout style="align-items: baseline">
      <strong style="flex: 1;">Employees</strong>
      <vaadin-context-menu
        open-on="click"
        .items="${this.contextMenuItems}"
        @item-selected="${(e: ContextMenuItemSelectedEvent) => {
          const value = e.detail.value as ContextMenuItem & { key: string };
          this.contextMenuItems = this.contextMenuItems.map((item) =>
            item.key === value.key ? { ...item, checked: !value.checked } : item
          );
        }}"
      >
        <vaadin-button theme="tertiary">Show/Hide Columns</vaadin-button>
      </vaadin-context-menu>
    </vaadin-horizontal-layout>

    <vaadin-grid .items="${this.items}">
      <vaadin-grid-column
        path="firstName"
        .hidden="${!this.contextMenuItems[0].checked}"
      ></vaadin-grid-column>
      <vaadin-grid-column
        path="lastName"
        .hidden="${!this.contextMenuItems[1].checked}"
      ></vaadin-grid-column>
      <vaadin-grid-column
        path="email"
        .hidden="${!this.contextMenuItems[2].checked}"
      ></vaadin-grid-column>
      <vaadin-grid-column
        path="address.phone"
        .hidden="${!this.contextMenuItems[3].checked}"
      ></vaadin-grid-column>
      <vaadin-grid-column
        path="profession"
        .hidden="${!this.contextMenuItems[4].checked}"
      ></vaadin-grid-column>
    </vaadin-grid>
  `;
}
```

**Java** — `GridColumnVisibility.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name");
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name");
Grid.Column<Person> emailColumn = grid.addColumn(Person::getEmail)
        .setHeader("Email");
Grid.Column<Person> phoneColumn = grid
        .addColumn(person -> person.getAddress().getPhone())
        .setHeader("Phone");
Grid.Column<Person> professionColumn = grid
        .addColumn(Person::getProfession).setHeader("Profession");

Button menuButton = new Button("Show/Hide Columns");
menuButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
ColumnToggleContextMenu columnToggleContextMenu = new ColumnToggleContextMenu(
        menuButton);
columnToggleContextMenu.addColumnToggleItem("First name",
        firstNameColumn);
columnToggleContextMenu.addColumnToggleItem("Last name",
        lastNameColumn);
columnToggleContextMenu.addColumnToggleItem("Email", emailColumn);
columnToggleContextMenu.addColumnToggleItem("Phone", phoneColumn);
columnToggleContextMenu.addColumnToggleItem("Profession",
        professionColumn);

...

private static class ColumnToggleContextMenu extends ContextMenu {
    public ColumnToggleContextMenu(Component target) {
        super(target);
        setOpenOnClick(true);
    }

    void addColumnToggleItem(String label, Grid.Column<Person> column) {
        MenuItem menuItem = this.addItem(label, e -> {
            column.setVisible(e.getSource().isChecked());
        });
        menuItem.setCheckable(true);
        menuItem.setChecked(column.isVisible());
    }
}
```

### <a id="column-reordering-resizing"></a>Column Reordering & Resizing

Enabling the user to reorder columns is useful when they want to compare data that isn’t adjacent by default. Grouped columns can only be reordered within their group.

Resizing is helpful when a column’s content doesn’t fit and is cut off or varies in length.

**TypeScript** — `grid-column-reordering-resizing.ts`

```typescript
@customElement('grid-column-reordering-resizing')
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;
  }

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

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

  protected override render() {
    return html`
      <vaadin-grid .items="${this.items}" column-reordering-allowed>
        <vaadin-grid-column-group header="Name">
          <vaadin-grid-column path="firstName" resizable></vaadin-grid-column>
          <vaadin-grid-column path="lastName" resizable></vaadin-grid-column>
        </vaadin-grid-column-group>
        <vaadin-grid-column-group header="Address">
          <vaadin-grid-column path="address.street" resizable></vaadin-grid-column>
          <vaadin-grid-column path="address.city" resizable></vaadin-grid-column>
          <vaadin-grid-column path="address.zip" resizable></vaadin-grid-column>
          <vaadin-grid-column path="address.state" resizable></vaadin-grid-column>
        </vaadin-grid-column-group>
      </vaadin-grid>
    `;
  }
}
```

**Java** — `GridColumnReorderingResizing.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.setColumnReorderingAllowed(true);
Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name")
        .setResizable(true);
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name").setResizable(true);
Grid.Column<Person> streetColumn = grid
        .addColumn(person -> person.getAddress().getStreet())
        .setHeader("Street").setResizable(true);
Grid.Column<Person> cityColumn = grid
        .addColumn(person -> person.getAddress().getCity())
        .setHeader("City").setResizable(true);
Grid.Column<Person> zipColumn = grid
        .addColumn(person -> person.getAddress().getZip())
        .setHeader("Zip").setResizable(true);
Grid.Column<Person> stateColumn = grid
        .addColumn(person -> person.getAddress().getState())
        .setHeader("State").setResizable(true);

HeaderRow headerRow = grid.prependHeaderRow();
headerRow.join(firstNameColumn, lastNameColumn).setText("Name");
headerRow.join(streetColumn, cityColumn, zipColumn, stateColumn)
        .setText("Address");
```

### <a id="column-width"></a>Column Width

All columns are the same width by default. You can set a specific width for any column, or allow the Grid to set the width automatically based on the contents.

Column widths can be fixed or non-fixed (default). Fixed width columns don’t grow or shrink as the available space changes, while non-fixed width columns do.

In the following example, the first and last columns have fixed widths. The second column’s width is set to be based on the content, while the third column takes up the remaining space.

**TypeScript** — `grid-column-width.ts`

```typescript
@customElement('grid-column-width')
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;
  }

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

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people.map((person) => ({
      ...person,
      displayName: `${person.firstName} ${person.lastName}`,
    }));
  }

  protected override render() {
    return html`
      <vaadin-split-layout>
        <vaadin-grid .items="${this.items}" style="width: 100%;">
          <vaadin-grid-selection-column></vaadin-grid-selection-column>
          <vaadin-grid-column path="firstName" width="7em" flex-grow="0"></vaadin-grid-column>
          <vaadin-grid-column path="profession" auto-width flex-grow="0"></vaadin-grid-column>
          <vaadin-grid-column path="email"></vaadin-grid-column>
          <vaadin-grid-column
            width="6em"
            flex-grow="0"
            header="Has Sub"
            ${columnBodyRenderer<Person>((item) => html`${item.subscriber ? 'Yes' : 'No'}`, [])}
          ></vaadin-grid-column>
        </vaadin-grid>
        <div></div>
      </vaadin-split-layout>
    `;
  }
}
```

**Java** — `GridColumnWidth.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.setSelectionMode(Grid.SelectionMode.MULTI);
grid.addColumn(Person::getFirstName).setHeader("First name")
        .setWidth("7em").setFlexGrow(0);
grid.addColumn(Person::getProfession).setHeader("Profession")
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(Person::getEmail).setHeader("Email");
grid.addColumn(person -> person.isSubscriber() ? "Yes" : "No")
        .setHeader("Has Sub").setWidth("6em").setFlexGrow(0);
```

## <a id="sorting"></a>Sorting

Any column can be made sortable. Enable sorting to allow the user to sort items alphabetically, numerically, by date, etc.

For detailed information on how to configure sorting in Flow, see [Column Sorting](https://vaadin.com/docs/v23/components/grid/flow.md#column-sorting).

**TypeScript** — `grid-sorting.ts`

```typescript
<vaadin-grid .items="${this.items}">
  <vaadin-grid-sort-column path="id"></vaadin-grid-sort-column>
  <vaadin-grid-sort-column path="displayName" header="Name"></vaadin-grid-sort-column>
  <vaadin-grid-sort-column path="email"></vaadin-grid-sort-column>
  <vaadin-grid-sort-column path="profession"></vaadin-grid-sort-column>
  <vaadin-grid-sort-column path="birthday"></vaadin-grid-sort-column>
</vaadin-grid>
```

**Java** — `GridSorting.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getId).setHeader("Id").setSortable(true);
grid.addColumn(Person::getFullName).setHeader("Name").setSortable(true);
grid.addColumn(Person::getEmail).setHeader("Email").setSortable(true);
grid.addColumn(Person::getProfession).setHeader("Profession")
        .setSortable(true);
grid.addColumn(new LocalDateRenderer<>(GridSorting::getPersonBirthday,
        "yyyy-MM-dd")).setHeader("Birthday").setSortable(true)
        .setComparator(Person::getBirthday);
```

### <a id="sorting-by-multiple-columns"></a>Sorting by Multiple Columns

The Grid can be sorted by multiple columns simultaneously by enabling multi-sort mode. When this is enabled, clicking a column header adds that column as a sort criterion instead of replacing the current sort column. A separate multi-sort on shift-click mode (since V23.3) combines the two by multi-sorting only when the column header is clicked while holding shift. The order in which multi-sort criteria (columns) are evaluated is determined by the multi-sort priority setting.

**TypeScript** — `grid-multisort.ts`

```typescript
<vaadin-grid .items="${this.items}" multi-sort multi-sort-priority="append">
  <vaadin-grid-sort-column path="id"></vaadin-grid-sort-column>
  <vaadin-grid-sort-column path="displayName" header="Name"></vaadin-grid-sort-column>
  <vaadin-grid-sort-column path="email"></vaadin-grid-sort-column>
  <vaadin-grid-sort-column path="profession"></vaadin-grid-sort-column>
  <vaadin-grid-sort-column path="birthday"></vaadin-grid-sort-column>
</vaadin-grid>
```

**Java** — `GridMultiSort.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getId).setHeader("Id").setSortable(true);
grid.addColumn(Person::getFullName).setHeader("Name").setSortable(true);
grid.addColumn(Person::getEmail).setHeader("Email").setSortable(true);
grid.addColumn(Person::getProfession).setHeader("Profession")
        .setSortable(true);
grid.addColumn(new LocalDateRenderer<>(GridMultiSort::getPersonBirthday,
        "yyyy-MM-dd")).setHeader("Birthday").setSortable(true)
        .setComparator(Person::getBirthday);
grid.setMultiSort(true, MultiSortPriority.APPEND);
```

> **Note: Shift-Click Multi-Sorting has Accessibility Issues**
>
> The multi-sort on shift-click mode is not recommended for applications for which accessibility is important. The feature is unlikely to work well with assistive technologies, and the lack of visual affordances makes it difficult to discover for sighted users.

### <a id="specifying-the-sort-property"></a>Specifying the Sort Property

Columns with rich or custom content can be sorted by defining the property by which to sort. For example, you can have a column containing a person’s profile picture, name and email sorted by the person’s last name.

**TypeScript** — `grid-rich-content-sorting.ts`

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

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

protected override render() {
  return html`
    <vaadin-grid .items="${this.items}">
      <vaadin-grid-sort-column
        header="Employee"
        path="lastName"
        ${columnBodyRenderer(this.employeeRenderer, [])}
      ></vaadin-grid-sort-column>
      <vaadin-grid-column
        ${columnHeaderRenderer(this.birthdayHeaderRenderer, [])}
        ${columnBodyRenderer(this.birthdayRenderer, [])}
      ></vaadin-grid-column>
    </vaadin-grid>
  `;
}

private employeeRenderer: GridColumnBodyLitRenderer<Person> = (person) => html`
  <vaadin-horizontal-layout style="align-items: center;" theme="spacing">
    <vaadin-avatar
      img="${person.pictureUrl}"
      name="${person.firstName} ${person.lastName}"
      alt="User avatar"
    ></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.email}
      </span>
    </vaadin-vertical-layout>
  </vaadin-horizontal-layout>
`;

private birthdayHeaderRenderer = () => html`
  <vaadin-grid-sorter path="birthday">Birthdate</vaadin-grid-sorter>
`;

private birthdayRenderer: GridColumnBodyLitRenderer<Person> = (person) => {
  const birthday = parseISO(person.birthday);
  return html`
    <vaadin-vertical-layout style="line-height: var(--lumo-line-height-m);">
      <span> ${format(birthday, 'P')} </span>
      <span style="font-size: var(--lumo-font-size-s); color: var(--lumo-secondary-text-color);">
        Age: ${differenceInYears(Date.now(), birthday)}
      </span>
    </vaadin-vertical-layout>
  `;
};
```

**Java** — `GridRichContentSorting.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(createEmployeeRenderer()).setHeader("Employee")
        .setAutoWidth(true).setFlexGrow(0)
        .setComparator(Person::getLastName);
grid.addColumn(createBirthdayRenderer()).setHeader("Birthdate")
        .setComparator(Person::getBirthday);
```

Sorting helps users find and analyze data. Therefore, it’s recommended to enable sorting for all applicable columns. An exception would be where the order is an essential part of the data itself, such as with prioritized lists.

## <a id="filtering"></a>Filtering

Filtering allows the user to find quickly a specific item or subset of items. You can add filters to Grid columns or use external filter fields.

**TypeScript** — `grid-column-filtering.ts`

```typescript
<vaadin-grid .items="${this.items}">
  <vaadin-grid-filter-column
    header="Name"
    path="displayName"
    flex-grow="0"
    width="230px"
    ${columnBodyRenderer(this.nameRenderer, [])}
  ></vaadin-grid-filter-column>
  <vaadin-grid-filter-column path="email"></vaadin-grid-filter-column>
  <vaadin-grid-filter-column path="profession"></vaadin-grid-filter-column>
</vaadin-grid>
```

**Java** — `GridColumnFiltering.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
Grid.Column<Person> nameColumn = grid.addColumn(createPersonRenderer())
        .setWidth("230px").setFlexGrow(0);
Grid.Column<Person> emailColumn = grid.addColumn(Person::getEmail);
Grid.Column<Person> professionColumn = grid
        .addColumn(Person::getProfession);

List<Person> people = DataService.getPeople();
GridListDataView<Person> dataView = grid.setItems(people);
PersonFilter personFilter = new PersonFilter(dataView);

grid.getHeaderRows().clear();
HeaderRow headerRow = grid.appendHeaderRow();

headerRow.getCell(nameColumn).setComponent(
        createFilterHeader("Name", personFilter::setFullName));
headerRow.getCell(emailColumn).setComponent(
        createFilterHeader("Email", personFilter::setEmail));
headerRow.getCell(professionColumn).setComponent(
        createFilterHeader("Profession", personFilter::setProfession));

...

private static Component createFilterHeader(String labelText,
        Consumer<String> filterChangeConsumer) {
    Label label = new Label(labelText);
    label.getStyle().set("padding-top", "var(--lumo-space-m)")
            .set("font-size", "var(--lumo-font-size-xs)");
    TextField textField = new TextField();
    textField.setValueChangeMode(ValueChangeMode.EAGER);
    textField.setClearButtonVisible(true);
    textField.addThemeVariants(TextFieldVariant.LUMO_SMALL);
    textField.setWidthFull();
    textField.getStyle().set("max-width", "100%");
    textField.addValueChangeListener(
            e -> filterChangeConsumer.accept(e.getValue()));
    VerticalLayout layout = new VerticalLayout(label, textField);
    layout.getThemeList().clear();
    layout.getThemeList().add("spacing-xs");

    return layout;
}

private static class PersonFilter {
    private final GridListDataView<Person> dataView;

    private String fullName;
    private String email;
    private String profession;

    public PersonFilter(GridListDataView<Person> dataView) {
        this.dataView = dataView;
        this.dataView.addFilter(this::test);
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
        this.dataView.refreshAll();
    }

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

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

    public boolean test(Person person) {
        boolean matchesFullName = matches(person.getFullName(), fullName);
        boolean matchesEmail = matches(person.getEmail(), email);
        boolean matchesProfession = matches(person.getProfession(),
                profession);

        return matchesFullName && matchesEmail && matchesProfession;
    }

    private boolean matches(String value, String searchTerm) {
        return searchTerm == null || searchTerm.isEmpty()
                || value.toLowerCase().contains(searchTerm.toLowerCase());
    }
}
```

Place filters outside the grid when the filter is based on multiple columns, or when a bigger field or more complex filter UI is needed, one which wouldn’t fit well in a column.

**TypeScript** — `grid-external-filtering.ts`

```typescript
@state()
private filteredItems: PersonEnhanced[] = [];

private items: PersonEnhanced[] = [];

protected override async firstUpdated() {
  const { people } = await getPeople();
  const items = people.map((person) => ({
    ...person,
    displayName: `${person.firstName} ${person.lastName}`,
  }));
  this.items = items;
  this.filteredItems = items;
}

protected override render() {
  return html`
    <vaadin-vertical-layout theme="spacing">
      <vaadin-text-field
        placeholder="Search"
        style="width: 50%;"
        @value-changed="${(e: TextFieldValueChangedEvent) => {
          const searchTerm = (e.detail.value || '').trim();
          const matchesTerm = (value: string) =>
            value.toLowerCase().includes(searchTerm.toLowerCase());

          this.filteredItems = this.items.filter(
            ({ displayName, email, profession }) =>
              !searchTerm ||
              matchesTerm(displayName) ||
              matchesTerm(email) ||
              matchesTerm(profession)
          );
        }}"
      >
        <vaadin-icon slot="prefix" icon="vaadin:search"></vaadin-icon>
      </vaadin-text-field>
      <vaadin-grid .items="${this.filteredItems}">
        <vaadin-grid-column
          header="Name"
          flex-grow="0"
          width="230px"
          ${columnBodyRenderer(this.nameRenderer, [])}
        ></vaadin-grid-column>
        <vaadin-grid-column path="email"></vaadin-grid-column>
        <vaadin-grid-column path="profession"></vaadin-grid-column>
      </vaadin-grid>
    </vaadin-vertical-layout>
  `;
}
```

**Java** — `GridExternalFiltering.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(createPersonRenderer()).setHeader("Name").setFlexGrow(0)
        .setWidth("230px");
grid.addColumn(Person::getEmail).setHeader("Email");
grid.addColumn(Person::getProfession).setHeader("Profession");

List<Person> people = DataService.getPeople();
GridListDataView<Person> dataView = grid.setItems(people);

TextField searchField = new TextField();
searchField.setWidth("50%");
searchField.setPlaceholder("Search");
searchField.setPrefixComponent(new Icon(VaadinIcon.SEARCH));
searchField.setValueChangeMode(ValueChangeMode.EAGER);
searchField.addValueChangeListener(e -> dataView.refreshAll());

dataView.addFilter(person -> {
    String searchTerm = searchField.getValue().trim();

    if (searchTerm.isEmpty())
        return true;

    boolean matchesFullName = matchesTerm(person.getFullName(),
            searchTerm);
    boolean matchesEmail = matchesTerm(person.getEmail(), searchTerm);
    boolean matchesProfession = matchesTerm(person.getProfession(),
            searchTerm);

    return matchesFullName || matchesEmail || matchesProfession;
});
```

## <a id="lazy-loading"></a>Lazy Loading

When you want to display a list of items which would be quite large to load entirely into memory, or you want to load items from a database, data providers can be used to provide lazy loading through pagination.

The following example uses a data provider for lazy loading, sorting, and filtering items:

**TypeScript** — `grid-data-provider.ts`

```typescript
async function fetchPeople(params: {
  page: number;
  pageSize: number;
  searchTerm: string;
  sortOrders: GridSorterDefinition[];
}) {
  const { page, pageSize, searchTerm, sortOrders } = params;
  const { people } = await getPeople();

  let result = people.map((person) => ({
    ...person,
    fullName: `${person.firstName} ${person.lastName}`,
  }));

  // Filtering
  if (searchTerm) {
    result = result.filter(
      (p) => matchesTerm(p.fullName, searchTerm) || matchesTerm(p.profession, searchTerm)
    );
  }

  // Sorting
  const sortBy = Object.fromEntries(sortOrders.map(({ path, direction }) => [path, direction]));
  if (sortBy.fullName) {
    result = result.sort((p1, p2) => compare(p1.fullName, p2.fullName, sortBy.fullName));
  } else if (sortBy.profession) {
    result = result.sort((p1, p2) => compare(p1.profession, p2.profession, sortBy.profession));
  }

  // Pagination
  const count = result.length;
  result = result.slice(page * pageSize, pageSize);

  return { people: result, count };
}

...

@state()
private searchTerm = '';

@query('#grid')
private grid!: Grid;

private dataProvider = async (
  params: GridDataProviderParams<Person>,
  callback: GridDataProviderCallback<Person>
) => {
  const { page, pageSize, sortOrders } = params;

  const { people, count } = await fetchPeople({
    page,
    pageSize,
    sortOrders,
    searchTerm: this.searchTerm,
  });

  callback(people, count);
};

protected override render() {
  return html`
    <vaadin-vertical-layout theme="spacing">
      <vaadin-text-field
        placeholder="Search"
        style="width: 50%;"
        @value-changed="${(e: TextFieldValueChangedEvent) => {
          this.searchTerm = (e.detail.value || '').trim();
          this.grid.clearCache();
        }}"
      >
        <vaadin-icon slot="prefix" icon="vaadin:search"></vaadin-icon>
      </vaadin-text-field>
      <vaadin-grid id="grid" .dataProvider="${this.dataProvider}">
        <vaadin-grid-sort-column path="fullName" header="Name"></vaadin-grid-sort-column>
        <vaadin-grid-sort-column path="profession"></vaadin-grid-sort-column>
      </vaadin-grid>
    </vaadin-vertical-layout>
  `;
}
```

**Java** — `GridDataProvider.java`

```java
private PersonFilter personFilter = new PersonFilter();

private PersonDataProvider dataProvider = new PersonDataProvider();

private ConfigurableFilterDataProvider<Person, Void, PersonFilter> filterDataProvider = dataProvider
        .withConfigurableFilter();

public GridDataProvider() {
    Grid<Person> grid = new Grid<>();
    grid.addColumn(Person::getFullName, "name").setHeader("Name");
    grid.addColumn(Person::getProfession, "profession").setHeader("Profession");
    grid.setItems(filterDataProvider);

    TextField searchField = new TextField();
    searchField.setWidth("50%");
    searchField.setPlaceholder("Search");
    searchField.setPrefixComponent(new Icon(VaadinIcon.SEARCH));
    searchField.setValueChangeMode(ValueChangeMode.EAGER);
    searchField.addValueChangeListener(e -> {
        personFilter.setSearchTerm(e.getValue());
        filterDataProvider.setFilter(personFilter);
    });

    VerticalLayout layout = new VerticalLayout(searchField, grid);
    layout.setPadding(false);
    add(layout);
}
```

**Java** — `PersonDataProvider.java`

```java
public class PersonDataProvider extends AbstractBackEndDataProvider<Person, PersonFilter> {
    final List<Person> DATABASE = new ArrayList<>(DataService.getPeople());

    @Override
    protected Stream<Person> fetchFromBackEnd(Query<Person, PersonFilter> query) {
        // A real app should use a real database or a service
        // to fetch, filter and sort data.
        Stream<Person> stream = DATABASE.stream();

        // Filtering
        if (query.getFilter().isPresent()) {
            stream = stream.filter(person -> query.getFilter().get().test(person));
        }

        // Sorting
        if (query.getSortOrders().size() > 0) {
            stream = stream.sorted(sortComparator(query.getSortOrders()));
        }

        // Pagination
        return stream.skip(query.getOffset()).limit(query.getLimit());
    }

    @Override
    protected int sizeInBackEnd(Query<Person, PersonFilter> query) {
        return (int) fetchFromBackEnd(query).count();
    }

    private static Comparator<Person> sortComparator(List<QuerySortOrder> sortOrders) {
        return sortOrders.stream().map(sortOrder -> {
            Comparator<Person> comparator = personFieldComparator(sortOrder.getSorted());

            if (sortOrder.getDirection() == SortDirection.DESCENDING) {
                comparator = comparator.reversed();
            }

            return comparator;
        }).reduce(Comparator::thenComparing).orElse((p1, p2) -> 0);
    }

    private static Comparator<Person> personFieldComparator(String sorted) {
        if (sorted.equals("name")) {
            return Comparator.comparing(person -> person.getFullName());
        } else if (sorted.equals("profession")) {
            return Comparator.comparing(person -> person.getProfession());
        }
        return (p1, p2) -> 0;
    }
}
```

**Java** — `PersonFilter.java`

```java
public class PersonFilter {
    private String searchTerm;

    public void setSearchTerm(String searchTerm) {
        this.searchTerm = searchTerm;
    }

    public boolean test(Person person) {
        boolean matchesFullName = matches(person.getFullName(), searchTerm);
        boolean matchesProfession = matches(person.getProfession(), searchTerm);
        return matchesFullName || matchesProfession;
    }

    private boolean matches(String value, String searchTerm) {
        return searchTerm == null || searchTerm.isEmpty()
                || value.toLowerCase().contains(searchTerm.toLowerCase());
    }
}
```

To learn more about data providers, see [Binding Items to Components](https://vaadin.com/docs/v23/binding-data/data-provider.md) (Java only).

## <a id="item-details"></a>Item Details

Item details are expandable content areas that can be displayed below the regular content of a row. They can be used to display more information about an item. By default, an item’s details are toggled by clicking on the item’s row.

**TypeScript** — `grid-item-details.ts`

```typescript
@customElement('grid-item-details')
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;
  }

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

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

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people.map((person) => ({
      ...person,
      displayName: `${person.firstName} ${person.lastName}`,
    }));
  }

  protected override render() {
    return html`
      <vaadin-grid
        theme="row-stripes"
        .items="${this.items}"
        .detailsOpenedItems="${this.detailsOpenedItem}"
        @active-item-changed="${(event: GridActiveItemChangedEvent<Person>) => {
          const person = event.detail.value;
          this.detailsOpenedItem = person ? [person] : [];
        }}"
        ${gridRowDetailsRenderer<Person>(
          (person) => html`
            <vaadin-form-layout .responsiveSteps="${[{ minWidth: '0', columns: 3 }]}">
              <vaadin-text-field
                label="Email address"
                .value="${person.email}"
                colspan="3"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="Phone number"
                .value="${person.address.phone}"
                colspan="3"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="Street address"
                .value="${person.address.street}"
                colspan="3"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="ZIP code"
                .value="${person.address.zip}"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="City"
                .value="${person.address.city}"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="State"
                .value="${person.address.state}"
                readonly
              ></vaadin-text-field>
            </vaadin-form-layout>
          `,
          []
        )}
      >
        <vaadin-grid-column path="displayName" header="Name"></vaadin-grid-column>
        <vaadin-grid-column path="profession"></vaadin-grid-column>
      </vaadin-grid>
    `;
  }
}
```

**Java** — `GridItemDetails.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFullName).setHeader("Name");
grid.addColumn(Person::getProfession).setHeader("Profession");

grid.setItemDetailsRenderer(createPersonDetailsRenderer());

...

private static ComponentRenderer<PersonDetailsFormLayout, Person> createPersonDetailsRenderer() {
    return new ComponentRenderer<>(PersonDetailsFormLayout::new,
            PersonDetailsFormLayout::setPerson);
}

private static class PersonDetailsFormLayout extends FormLayout {
    private final TextField emailField = new TextField("Email address");
    private final TextField phoneField = new TextField("Phone number");
    private final TextField streetField = new TextField("Street address");
    private final TextField zipField = new TextField("ZIP code");
    private final TextField cityField = new TextField("City");
    private final TextField stateField = new TextField("State");

    public PersonDetailsFormLayout() {
        Stream.of(emailField, phoneField, streetField, zipField, cityField,
                stateField).forEach(field -> {
                    field.setReadOnly(true);
                    add(field);
                });

        setResponsiveSteps(new ResponsiveStep("0", 3));
        setColspan(emailField, 3);
        setColspan(phoneField, 3);
        setColspan(streetField, 3);
    }

    public void setPerson(Person person) {
        emailField.setValue(person.getEmail());
        phoneField.setValue(person.getAddress().getPhone());
        streetField.setValue(person.getAddress().getStreet());
        zipField.setValue(person.getAddress().getZip());
        cityField.setValue(person.getAddress().getCity());
        stateField.setValue(person.getAddress().getState());
    }
}
```

The default toggle behavior can be replaced by programmatically toggling the details visibility, such as from a button click.

**TypeScript** — `grid-item-details-toggle.ts`

```typescript
@customElement('grid-item-details-toggle')
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;
  }

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

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

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people.map((person) => ({
      ...person,
      displayName: `${person.firstName} ${person.lastName}`,
    }));
  }

  protected override render() {
    return html`
      <vaadin-grid
        theme="row-stripes"
        .items="${this.items}"
        .detailsOpenedItems="${this.detailsOpenedItems}"
        ${gridRowDetailsRenderer<Person>(
          (person) => html`
            <vaadin-form-layout .responsiveSteps="${[{ minWidth: '0', columns: 3 }]}">
              <vaadin-text-field
                label="Email address"
                .value="${person.email}"
                colspan="3"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="Phone number"
                .value="${person.address.phone}"
                colspan="3"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="Street address"
                .value="${person.address.street}"
                colspan="3"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="ZIP code"
                .value="${person.address.zip}"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="City"
                .value="${person.address.city}"
                readonly
              ></vaadin-text-field>
              <vaadin-text-field
                label="State"
                .value="${person.address.state}"
                readonly
              ></vaadin-text-field>
            </vaadin-form-layout>
          `,
          []
        )}
      >
        <vaadin-grid-column path="displayName" header="Name"></vaadin-grid-column>
        <vaadin-grid-column path="profession"></vaadin-grid-column>
        <vaadin-grid-column
          ${columnBodyRenderer<Person>(
            (person) => html`
              <vaadin-button
                theme="tertiary"
                @click="${() => {
                  const isOpened = this.detailsOpenedItems.includes(person);
                  this.detailsOpenedItems = isOpened
                    ? this.detailsOpenedItems.filter((p) => p !== person)
                    : [...this.detailsOpenedItems, person];
                }}"
              >
                Toggle details
              </vaadin-button>
            `,
            []
          )}
        ></vaadin-grid-column>
      </vaadin-grid>
    `;
  }
}
```

**Java** — `GridItemDetailsToggle.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFullName).setHeader("Name");
grid.addColumn(Person::getProfession).setHeader("Profession");
grid.addColumn(createToggleDetailsRenderer(grid));

grid.setDetailsVisibleOnClick(false);
grid.setItemDetailsRenderer(createPersonDetailsRenderer());

...

private static Renderer<Person> createToggleDetailsRenderer(
        Grid<Person> grid) {
    return LitRenderer.<Person> of(
            "<vaadin-button theme=\"tertiary\" @click=\"${handleClick}\">Toggle details</vaadin-button>")
            .withFunction("handleClick",
                    person -> grid.setDetailsVisible(person,
                            !grid.isDetailsVisible(person)));
}
```

[Tooltips](#tooltips) can be used as a lightweight alternative to the item details panel.

## <a id="context-menu"></a>Context Menu

You can use Context Menu to provide shortcuts for the user. It appears on a right-click by default or a left click. In a mobile browser, a long press opens the menu.

Using a context menu shouldn’t be the only way of accomplishing a task. The same functionality needs to be accessible elsewhere in the UI.

See [Context Menu](https://vaadin.com/docs/v23/components/context-menu.md) for more information.

**TypeScript** — `grid-context-menu.ts`

```typescript
private renderMenu: ContextMenuLitRenderer = (context, menu) => {
  const { sourceEvent } = context.detail as { sourceEvent: Event };
  const grid = menu.firstElementChild as Grid<Person>;

  const eventContext = grid.getEventContext(sourceEvent);
  const person = eventContext.item!;

  const clickHandler = (_action: string) => () => {
    // console.log(`${action}: ${person.firstName} ${person.lastName}`);
  };

  return html`
    <vaadin-list-box>
      <vaadin-item @click="${clickHandler('Edit')}">Edit</vaadin-item>
      <vaadin-item @click="${clickHandler('Delete')}">Delete</vaadin-item>
      <hr />
      <vaadin-item @click="${clickHandler('Email')}">Email (${person.email})</vaadin-item>
      <vaadin-item @click="${clickHandler('Call')}">Call (${person.address.phone})</vaadin-item>
    </vaadin-list-box>
  `;
};

protected override render() {
  return html`
    <vaadin-context-menu ${contextMenuRenderer(this.renderMenu, [])}>
      <vaadin-grid .items="${this.items}" @vaadin-contextmenu="${this.onContextMenu}">
        <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-column path="profession"></vaadin-grid-column>
      </vaadin-grid>
    </vaadin-context-menu>
  `;
}

onContextMenu(e: MouseEvent) {
  // Prevent opening context menu on header row.
  if ((e.currentTarget as Grid).getEventContext(e).section !== 'body') {
    e.stopPropagation();
  }
}
```

**Java** — `GridContextMenuExample.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");
grid.addColumn(Person::getProfession).setHeader("Profession");

PersonContextMenu contextMenu = new PersonContextMenu(grid);

add(grid);

...

private static class PersonContextMenu extends GridContextMenu<Person> {
    public PersonContextMenu(Grid<Person> target) {
        super(target);

        addItem("Edit", e -> e.getItem().ifPresent(person -> {
            // System.out.printf("Edit: %s%n", person.getFullName());
        }));
        addItem("Delete", e -> e.getItem().ifPresent(person -> {
            // System.out.printf("Delete: %s%n", person.getFullName());
        }));

        add(new Hr());

        GridMenuItem<Person> emailItem = addItem("Email",
                e -> e.getItem().ifPresent(person -> {
                    // System.out.printf("Email: %s%n",
                    // person.getFullName());
                }));
        GridMenuItem<Person> phoneItem = addItem("Call",
                e -> e.getItem().ifPresent(person -> {
                    // System.out.printf("Phone: %s%n",
                    // person.getFullName());
                }));

        setDynamicContentHandler(person -> {
            // Do not show context menu when header is clicked
            if (person == null)
                return false;
            emailItem
                    .setText(String.format("Email: %s", person.getEmail()));
            phoneItem.setText(String.format("Call: %s",
                    person.getAddress().getPhone()));
            return true;
        });
    }
}
```

## <a id="tooltips"></a>Tooltips (since V23.3)

Tooltips on cells can be used for many different use cases: They can be used for providing additional details on the contents of a cell — if an [item details panel](#item-details) would be overkill or otherwise undesirable. They can provide the full text of a cell if it’s too long to fit feasibly into the cell itself — if [wrapping the cell contents](#wrap-cell-content) is insufficient or otherwise undesirable. Or they can provide textual explanations for non-text content, such as status icons.

**TypeScript** — `grid-tooltip-generator.ts`

```typescript
private tooltipGenerator = (context: GridEventContext<Person>): string => {
  let text = '';

  const { column, item } = context;
  if (column && item) {
    switch (column.path) {
      case 'birthday':
        text = `Age: ${differenceInYears(Date.now(), parseISO(item.birthday))}`;
        break;
      case 'status':
        text = item.status;
        break;
      default:
        break;
    }
  }

  return text;
};

...

<vaadin-tooltip slot="tooltip" .generator="${this.tooltipGenerator}"></vaadin-tooltip>
```

**Java** — `GridTooltipGenerator.java`

```java
grid.addColumn(person -> getFormattedPersonBirthday(person))
        .setTooltipGenerator(person -> "Age: " + getPersonAge(person))
        .setHeader("Birthday");
grid.addComponentColumn(person -> createStatusIcon(person.getStatus()))
        .setTooltipGenerator(person -> person.getStatus())
        .setHeader("Status");
```

See [Tooltips documentation](https://vaadin.com/docs/v23/components/tooltip.md) for details on tooltip configuration.

<!-- vale Vaadin.Wordiness = NO -->

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

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

<!-- vale Vaadin.Wordiness = YES -->

### <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:

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

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

You can drag rows to reorder them.

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

```typescript
@customElement('grid-row-reordering')
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;
  }

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

  @state()
  private draggedItem?: Person;

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

  protected override render() {
    return html`
      <vaadin-grid
        .items="${this.items}"
        rows-draggable
        drop-mode="between"
        @grid-dragstart="${(event: GridDragStartEvent<Person>) => {
          this.draggedItem = event.detail.draggedItems[0];
        }}"
        @grid-dragend="${() => {
          delete this.draggedItem;
        }}"
        @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}"
      alt="User avatar"
    ></vaadin-avatar>
  `;
}
```

**Java** — `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.setDropMode(GridDropMode.BETWEEN);
grid.setRowsDraggable(true);

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

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

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

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

```typescript
@state()
private draggedItem?: Person;

@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 = () => {
  delete this.draggedItem;
};

protected override render() {
  return html`
    <div class="grids-container">
      <vaadin-grid
        .items="${this.grid1Items}"
        rows-draggable
        drop-mode="on-grid"
        @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
        drop-mode="on-grid"
        @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>
  `;
}
```

**Java** — `GridDragRowsBetweenGrids.java`

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

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

grid1.setDropMode(GridDropMode.ON_GRID);
grid1.setRowsDraggable(true);
grid1.addDragStartListener(this::handleDragStart);
grid1.addDropListener(e -> {
    dataView2.removeItem(draggedItem);
    dataView1.addItem(draggedItem);
});
grid1.addDragEndListener(this::handleDragEnd);

grid2.setDropMode(GridDropMode.ON_GRID);
grid2.setRowsDraggable(true);
grid2.addDragStartListener(this::handleDragStart);
grid2.addDropListener(e -> {
    dataView1.removeItem(draggedItem);
    dataView2.addItem(draggedItem);
});
grid2.addDragEndListener(this::handleDragEnd);
```

<!-- vale Vaadin.Wordiness = NO -->

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

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

<!-- vale Vaadin.Wordiness = NO -->

**TypeScript** — `grid-drag-drop-filters.ts`

```typescript
@customElement('grid-drag-drop-filters')
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;

  @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}"
        .itemIdPath="${'id'}"
        .expandedItems="${this.expandedItems}"
        @expanded-items-changed="${(event: GridExpandedItemsChangedEvent<Person>) => {
          this.expandedItems = event.detail.value;
        }}"
        rows-draggable
        drop-mode="on-top"
        @grid-dragstart="${(event: GridDragStartEvent<Person>) => {
          this.draggedItem = event.detail.draggedItems[0];
        }}"
        @grid-dragend="${() => {
          delete this.draggedItem;
        }}"
        @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();
          }
        }}"
        .dragFilter="${(model: GridItemModel<Person>) => {
          const item = model.item;
          return !item.manager; // Only drag non-managers
        }}"
        .dropFilter="${(model: GridItemModel<Person>) => {
          const item = model.item;
          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"
          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>
    `;
  }
}
```

**Java** — `GridDragDropFilters.java`

```java
TreeGrid<Person> treeGrid = setupTreeGrid();

TreeData<Person> treeData = new TreeData<>();
treeData.addItems(managers, this::getStaff);
TreeDataProvider<Person> treeDataProvider = new TreeDataProvider<>(
        treeData);
treeGrid.setDataProvider(treeDataProvider);

treeGrid.setRowsDraggable(true);
treeGrid.setDropMode(GridDropMode.ON_TOP);
// Only allow dragging staff
treeGrid.setDragFilter(person -> !person.isManager());
// Only allow dropping on managers
treeGrid.setDropFilter(person -> person.isManager());

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

treeGrid.addDropListener(e -> {
    Person newManager = e.getDropTargetItem().orElse(null);
    boolean isSameManager = newManager != null
            && newManager.getId().equals(draggedItem.getManagerId());

    if (newManager == null || isSameManager)
        return;

    draggedItem.setManagerId(newManager.getId());
    treeData.removeItem(draggedItem);
    treeData.addItem(newManager, draggedItem);

    treeDataProvider.refreshAll();
});

treeGrid.addDragEndListener(e -> draggedItem = null);
```

## <a id="inline-editing-java-only"></a>Inline Editing (Java Only)

Grid can be configured to allow inline editing. Editing can be either buffered or non-buffered. Buffered means changes must be explicitly committed, while non-buffered automatically commit changes on blur — when a field loses focus.

### <a id="buffered"></a>Buffered

**Java** — `GridBufferedInlineEditor.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
Editor<Person> editor = grid.getEditor();

Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name")
        .setWidth("120px").setFlexGrow(0);
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name").setWidth("120px").setFlexGrow(0);
Grid.Column<Person> emailColumn = grid.addColumn(Person::getEmail)
        .setHeader("Email");
Grid.Column<Person> editColumn = grid.addComponentColumn(person -> {
    Button editButton = new Button("Edit");
    editButton.addClickListener(e -> {
        if (editor.isOpen())
            editor.cancel();
        grid.getEditor().editItem(person);
    });
    return editButton;
}).setWidth("150px").setFlexGrow(0);

Binder<Person> binder = new Binder<>(Person.class);
editor.setBinder(binder);
editor.setBuffered(true);

TextField firstNameField = new TextField();
firstNameField.setWidthFull();
binder.forField(firstNameField)
        .asRequired("First name must not be empty")
        .withStatusLabel(firstNameValidationMessage)
        .bind(Person::getFirstName, Person::setFirstName);
firstNameColumn.setEditorComponent(firstNameField);

TextField lastNameField = new TextField();
lastNameField.setWidthFull();
binder.forField(lastNameField).asRequired("Last name must not be empty")
        .withStatusLabel(lastNameValidationMessage)
        .bind(Person::getLastName, Person::setLastName);
lastNameColumn.setEditorComponent(lastNameField);

EmailField emailField = new EmailField();
emailField.setWidthFull();
binder.forField(emailField).asRequired("Email must not be empty")
        .withValidator(
                new EmailValidator("Enter a valid email address"))
        .withStatusLabel(emailValidationMessage)
        .bind(Person::getEmail, Person::setEmail);
emailColumn.setEditorComponent(emailField);

Button saveButton = new Button("Save", e -> editor.save());
Button cancelButton = new Button(VaadinIcon.CLOSE.create(),
        e -> editor.cancel());
cancelButton.addThemeVariants(ButtonVariant.LUMO_ICON,
        ButtonVariant.LUMO_ERROR);
HorizontalLayout actions = new HorizontalLayout(saveButton,
        cancelButton);
actions.setPadding(false);
editColumn.setEditorComponent(actions);
```

**Java** — `ValidationMessage.java`

```java
package com.vaadin.demo.component.grid;

import com.vaadin.flow.component.HasText;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;

class ValidationMessage extends HorizontalLayout implements HasText {

    private final Span span = new Span();

    public ValidationMessage() {
        setVisible(false);
        setAlignItems(Alignment.CENTER);
        getStyle().set("color", "var(--lumo-error-text-color)");
        getThemeList().clear();
        getThemeList().add("spacing-s");

        Icon icon = VaadinIcon.EXCLAMATION_CIRCLE_O.create();
        icon.setSize("16px");
        add(icon, span);
    }

    @Override
    public String getText() {
        return span.getText();
    }

    @Override
    public void setText(String text) {
        span.setText(text);
        this.setVisible(text != null && !text.isEmpty());
    }
}
```

### <a id="non-buffered"></a>Non-Buffered

In the example below, double-click a row to start editing. Press `Escape`, or click on a different row to stop editing.

**Java** — `GridUnbufferedInlineEditor.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name")
        .setWidth("120px").setFlexGrow(0);
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name").setWidth("120px").setFlexGrow(0);
Grid.Column<Person> emailColumn = grid.addColumn(Person::getEmail)
        .setHeader("Email");

Binder<Person> binder = new Binder<>(Person.class);
Editor<Person> editor = grid.getEditor();
editor.setBinder(binder);

TextField firstNameField = new TextField();
firstNameField.setWidthFull();
addCloseHandler(firstNameField, editor);
binder.forField(firstNameField)
        .asRequired("First name must not be empty")
        .withStatusLabel(firstNameValidationMessage)
        .bind(Person::getFirstName, Person::setFirstName);
firstNameColumn.setEditorComponent(firstNameField);

TextField lastNameField = new TextField();
lastNameField.setWidthFull();
addCloseHandler(lastNameField, editor);
binder.forField(lastNameField).asRequired("Last name must not be empty")
        .withStatusLabel(lastNameValidationMessage)
        .bind(Person::getLastName, Person::setLastName);
lastNameColumn.setEditorComponent(lastNameField);

EmailField emailField = new EmailField();
emailField.setWidthFull();
addCloseHandler(emailField, editor);
binder.forField(emailField).asRequired("Email must not be empty")
        .withValidator(
                new EmailValidator("Enter a valid email address"))
        .withStatusLabel(emailValidationMessage)
        .bind(Person::getEmail, Person::setEmail);
emailColumn.setEditorComponent(emailField);

grid.addItemDoubleClickListener(e -> {
    editor.editItem(e.getItem());
    Component editorComponent = e.getColumn().getEditorComponent();
    if (editorComponent instanceof Focusable) {
        ((Focusable) editorComponent).focus();
    }
});
```

**Java** — `ValidationMessage.java`

```java
package com.vaadin.demo.component.grid;

import com.vaadin.flow.component.HasText;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;

class ValidationMessage extends HorizontalLayout implements HasText {

    private final Span span = new Span();

    public ValidationMessage() {
        setVisible(false);
        setAlignItems(Alignment.CENTER);
        getStyle().set("color", "var(--lumo-error-text-color)");
        getThemeList().clear();
        getThemeList().add("spacing-s");

        Icon icon = VaadinIcon.EXCLAMATION_CIRCLE_O.create();
        icon.setSize("16px");
        add(icon, span);
    }

    @Override
    public String getText() {
        return span.getText();
    }

    @Override
    public void setText(String text) {
        span.setText(text);
        this.setVisible(text != null && !text.isEmpty());
    }
}
```

Alternatively, use [Grid Pro](https://vaadin.com/docs/v23/components/grid-pro.md) for more streamlined inline-editing, or [CRUD](https://vaadin.com/docs/v23/components/crud.md) for editing in a separate side panel or dialog.

## <a id="styling-rows-columns"></a>Styling Rows & Columns

You can style individual cells based on the data. You might do this, for example, to highlight changes or important information.

**TypeScript** — `grid-styling.ts`

```typescript
interface PersonWithRating extends Person {
  customerRating: number;
}

@customElement('grid-styling')
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;
  }

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

  private ratingFormatter = new Intl.NumberFormat('en-US', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });

  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people.map((person) => ({ ...person, customerRating: Math.random() * 10 }));
  }

  protected override render() {
    return html`
      <vaadin-grid .items="${this.items}" .cellClassNameGenerator="${this.cellClassNameGenerator}">
        <vaadin-grid-column path="firstName"></vaadin-grid-column>
        <vaadin-grid-column path="lastName"></vaadin-grid-column>
        <vaadin-grid-column path="profession"></vaadin-grid-column>
        <vaadin-grid-column
          header="Customer rating (0-10)"
          ${columnBodyRenderer(this.ratingRenderer, [])}
        ></vaadin-grid-column>
      </vaadin-grid>
    `;
  }

  private ratingRenderer: GridColumnBodyLitRenderer<PersonWithRating> = (person) => html`
    <span>${this.ratingFormatter.format(person.customerRating)}</span>
  `;

  private cellClassNameGenerator(column: GridColumn, model: GridItemModel<PersonWithRating>) {
    const item = model.item;
    let classes = '';
    // Make the customer rating column bold
    if (column.header?.startsWith('Customer rating')) {
      classes += ' font-weight-bold';
    }
    // Add high-rating class to customer ratings of 8 or higher
    if (item.customerRating >= 8.0) {
      classes += ' high-rating';
      // Add low-rating class to customer ratings of 4 or lower
    } else if (item.customerRating <= 4.0) {
      classes += ' low-rating';
    }
    return classes;
  }
}
```

**Java** — `GridStyling.java`

```java
Grid<PersonWithRating> grid = new Grid<>(PersonWithRating.class, false);
grid.addColumn(PersonWithRating::getFirstName).setHeader("First name");
grid.addColumn(PersonWithRating::getLastName).setHeader("Last name");
grid.addColumn(PersonWithRating::getProfession).setHeader("Profession");
grid.addColumn(PersonWithRating::getFormattedRating)
        .setHeader("Customer rating (0-10)");

grid.setClassNameGenerator(person -> {
    if (person.getRating() >= 8)
        return "high-rating";
    if (person.getRating() <= 4)
        return "low-rating";
    return null;
});
```

`vaadin-grid-styling.css`

```css
/* frontend/theme/[my-theme]/components/vaadin-grid.css */

.high-rating {
    background-color: var(--lumo-success-color-10pct);
}

.low-rating {
    background-color: var(--lumo-error-color-10pct);
}

.font-weight-bold {
    font-weight: bold;
}
```

## <a id="theme-variants"></a>Theme Variants

Grid variants can reduce the white space inside the grid, adjust the border and row to highlight visibility, and control cell content overflow behavior.

Variants can be combined together.

### <a id="compact"></a>Compact

The `compact` theme variant makes a grid more dense by reducing the header and row heights, as well as the spacing between columns.

This is useful for displaying more information on-screen without having to scroll. It can also help improve scannability and comparability between rows.

**TypeScript** — `grid-compact.ts`

```typescript
<vaadin-grid .items="${this.items}" theme="compact">
  <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>
```

**Java** — `GridCompact.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");

grid.addThemeVariants(GridVariant.LUMO_COMPACT);
```

### <a id="no-border"></a>No Border

The `no-border` theme variant removes the outer border of the grid.

**TypeScript** — `grid-no-border.ts`

```typescript
<vaadin-grid .items="${this.items}" theme="no-border">
  <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>
```

**Java** — `GridNoBorder.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(createAvatarRenderer()).setHeader("Image")
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");

grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
```

### <a id="no-row-border"></a>No Row Border

This theme variant removes the horizontal row borders. It’s best suited for small datasets. Parsing larger datasets may be difficult unless paired with the `row-stripes` theme variant.

**TypeScript** — `grid-no-row-border.ts`

```typescript
<vaadin-grid .items="${this.items}" theme="no-row-borders">
  <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>
```

**Java** — `GridNoRowBorder.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(createAvatarRenderer()).setHeader("Image")
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");

grid.addThemeVariants(GridVariant.LUMO_NO_ROW_BORDERS);
```

### <a id="column-borders"></a>Column Borders

You can add vertical borders between columns by using the `column-borders` theme variant. Datasets with a lot of columns packed tightly together, or where content is truncated, can benefit from the additional separation that vertical borders bring.

**TypeScript** — `grid-column-borders.ts`

```typescript
<vaadin-grid .items="${this.items}" theme="column-borders">
  <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>
```

**Java** — `GridColumnBorders.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(createAvatarRenderer()).setHeader("Image")
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");

grid.addThemeVariants(GridVariant.LUMO_COLUMN_BORDERS);
```

### <a id="row-stripes"></a>Row Stripes

The `row-stripes` theme produces a background color for every other row. This can have a positive effect on scannability.

**TypeScript** — `grid-row-stripes.ts`

```typescript
<vaadin-grid .items="${this.items}" theme="row-stripes">
  <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>
```

**Java** — `GridRowStripes.java`

```java
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(createAvatarRenderer()).setHeader("Image")
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");

grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES);
```

## <a id="cell-focus"></a>Cell Focus

Cells can be focused by clicking on a cell or with the keyboard.

The following keyboard shortcuts are available:

|                                              |                                                                           |
| -------------------------------------------- | ------------------------------------------------------------------------- |
| `Tab`                                        | Switches focus between sections of the grid (i.e., header, body, footer). |
| `Left`, `Up`, `Right`, and `Down` Arrow Keys | Moves focus between cells within a section of the grid.                   |
| `Page Up`                                    | Moves cell focus up by one page of visible rows.                          |
| `Page Down`                                  | Moves cell focus down by one page of visible rows.                        |
| `Home`                                       | Moves focus to the first cell in a row.                                   |
| `End`                                        | Moves focus to the last cell in a row.                                    |

The cell focus event can be used to get notified when the user changes focus between cells.

By default, the focus outline is only visible when using keyboard navigation. For illustrative purposes, the example below also uses custom styles to show the focus outline when clicking on cells:

**TypeScript** — `grid-cell-focus.ts`

```typescript
protected override render() {
  return html`
    <vaadin-grid
      theme="force-focus-outline"
      .items="${this.items}"
      @cell-focus="${(e: GridCellFocusEvent<Person>) => {
        const eventContext = this.grid.getEventContext(e);
        const section = eventContext.section ?? 'Not available';
        const row = eventContext.index != null ? eventContext.index : 'Not available';
        const column = eventContext.column?.path ?? 'Not available';
        const person = eventContext.item;
        const fullName =
          person?.firstName && person?.lastName
            ? `${person.firstName} ${person.lastName}`
            : 'Not available';

        this.eventSummary = `Section: ${section}\nRow: ${row}\nColumn: ${column}\nPerson: ${fullName}`;
      }}"
    >
      <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-column path="profession"></vaadin-grid-column>
    </vaadin-grid>
    <div>
      <vaadin-text-area
        label="Cell focus event information"
        readonly
        .value="${this.eventSummary}"
      ></vaadin-text-area>
    </div>
  `;
}
```

**Java** — `GridCellFocus.java`

```java
grid.addCellFocusListener(event -> {
    CellFocusEvent.GridSection section = event.getSection();
    String column = event.getColumn().map(Grid.Column::getKey)
            .orElse("Not available");
    String row = event.getItem()
            .map(value -> String.valueOf(people.indexOf(value)))
            .orElse("Not available");
    String fullName = event.getItem().map(Person::getFullName)
            .orElse("Not available");

    String eventSummary = String.format(
            "Section: %s%nRow: %s%nColumn: %s%nPerson: %s", section,
            row, column, fullName);

    textArea.setValue(eventSummary);
});
```

`vaadin-grid-cell-focus.css`

```css
/* frontend/theme/[my-theme]/components/vaadin-grid.css */

:host([theme~='force-focus-outline']) [part~='cell']:focus::before {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    pointer-events: none;
    box-shadow: inset 0 0 0 2px var(--lumo-primary-color-50pct);
}
```

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

| Component                                                        | Usage Recommendations                                                   |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------- |
| [CRUD](https://vaadin.com/docs/v23/components/crud.md)           | Component for creating, displaying, updating and deleting tabular data. |
| [Grid Pro](https://vaadin.com/docs/v23/components/grid-pro.md)   | Component for showing and editing tabular data.                         |
| [Tree Grid](https://vaadin.com/docs/v23/components/tree-grid.md) | Component for showing hierarchical tabular data.                        |
| [List Box](https://vaadin.com/docs/v23/components/list-box.md)   | Lightweight component for lightweight, single-column lists.             |

`AC63AABF-4102-4C3E-9776-A09DDC04EF37`
