PopoverAnchoredDialog.java
package com.vaadin.demo.component.popover;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.checkbox.CheckboxGroupVariant;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.popover.Popover;
import com.vaadin.flow.component.popover.PopoverPosition;
import com.vaadin.flow.data.renderer.LocalDateRenderer;
import com.vaadin.flow.router.Route;
@Route("popover-anchored-dialog")
public class PopoverAnchoredDialog extends Div {
    public PopoverAnchoredDialog() {
        // tag::gridsnippet[]
        Grid<Person> grid = new Grid<>(Person.class, false);
        grid.addColumn(Person::getFirstName).setKey("firstName")
                .setHeader("First name");
        grid.addColumn(Person::getLastName).setKey("lastName")
                .setHeader("Last name");
        grid.addColumn(Person::getEmail).setKey("email").setHeader("Email");
        grid.addColumn(person -> person.getAddress().getPhone()).setKey("phone")
                .setHeader("Phone");
        grid.addColumn(new LocalDateRenderer<>(
                PopoverAnchoredDialog::getPersonBirthday, "yyyy-MM-dd"))
                .setKey("birthday").setHeader("Birthday");
        grid.addColumn(Person::getProfession).setKey("profession")
                .setHeader("Profession");
        // end::gridsnippet[]
        grid.setItems(DataService.getPeople());
        H3 title = new H3("Employees");
        Button button = new Button(VaadinIcon.GRID_H.create());
        button.addThemeVariants(ButtonVariant.LUMO_ICON);
        button.setAriaLabel("Show / hide columns");
        HorizontalLayout headerLayout = new HorizontalLayout(title, button);
        headerLayout.setAlignItems(FlexComponent.Alignment.BASELINE);
        headerLayout.setFlexGrow(1, title);
        Popover popover = new Popover();
        popover.setModal(true);
        popover.setBackdropVisible(true);
        popover.setPosition(PopoverPosition.BOTTOM_END);
        popover.setTarget(button);
        Div heading = new Div("Configure columns");
        heading.getStyle().set("font-weight", "600");
        heading.getStyle().set("padding", "var(--lumo-space-xs)");
        List<String> columns = List.of("firstName", "lastName", "email",
                "phone", "birthday", "profession");
        // tag::gridsnippet[]
        
        CheckboxGroup<String> group = new CheckboxGroup<>();
        group.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
        group.setItems(columns);
        group.setItemLabelGenerator((item) -> {
            String label = StringUtils
                    .join(StringUtils.splitByCharacterTypeCamelCase(item), " ");
            return StringUtils.capitalize(label.toLowerCase());
        });
        group.addValueChangeListener((e) -> {
            columns.stream().forEach((key) -> {
                grid.getColumnByKey(key).setVisible(e.getValue().contains(key));
            });
        });
        // end::gridsnippet[]
        Set<String> defaultColumns = Set.of("firstName", "lastName", "email",
                "profession");
        group.setValue(defaultColumns);
        Button showAll = new Button("Show all", (e) -> {
            group.setValue(new HashSet<String>(columns));
        });
        showAll.addThemeVariants(ButtonVariant.LUMO_SMALL);
        Button reset = new Button("Reset", (e) -> {
            group.setValue(defaultColumns);
        });
        reset.addThemeVariants(ButtonVariant.LUMO_SMALL);
        HorizontalLayout footer = new HorizontalLayout(showAll, reset);
        footer.setSpacing(false);
        footer.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
        popover.add(heading, group, footer);
        add(headerLayout, grid, popover);
    }
    private static LocalDate getPersonBirthday(Person person) {
        return person.getBirthday().toInstant().atZone(ZoneId.systemDefault())
                .toLocalDate();
    }
}
popover-anchored-dialog.tsx
import '@vaadin/icons';
import React, { useEffect } from 'react';
import { useComputed, useSignal } from '@vaadin/hilla-react-signals';
import {
  Button,
  Checkbox,
  CheckboxGroup,
  Grid,
  GridColumn,
  HorizontalLayout,
  Icon,
  Popover,
} from '@vaadin/react-components';
import { getPeople } from 'Frontend/demo/domain/DataService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
type ColumnConfig = { label: string; key: string; visible: boolean };
const DEFAULT_COLUMNS: ColumnConfig[] = [
  { label: 'First name', key: 'firstName', visible: true },
  { label: 'Last name', key: 'lastName', visible: true },
  { label: 'Email', key: 'email', visible: true },
  { label: 'Phone', key: 'address.phone', visible: false },
  { label: 'Birthday', key: 'birthday', visible: false },
  { label: 'Profession', key: 'profession', visible: true },
];
function Example() {
  const items = useSignal<Person[]>([]);
  const columns = useSignal<ColumnConfig[]>([...DEFAULT_COLUMNS]);
  const visibleColumns = useComputed(() =>
    columns.value.filter((column) => column.visible).map((column) => column.key)
  );
  useEffect(() => {
    getPeople().then(({ people }) => {
      items.value = people;
    });
  }, []);
  return (
    <>
      <HorizontalLayout style={{ alignItems: 'baseline' }}>
        <h3 style={{ flex: 1 }}>Employees</h3>
        <Button id="toggle-columns" theme="icon" aria-label="Show / hide columns">
          <Icon icon="vaadin:grid-h" />
        </Button>
      </HorizontalLayout>
      <Popover for="toggle-columns" modal withBackdrop position="bottom-end">
        <div style={{ fontWeight: '600', padding: 'var(--lumo-space-xs)' }}>Configure columns</div>
        <CheckboxGroup theme="vertical" value={visibleColumns.value}>
          {columns.value.map((item) => (
            <Checkbox
              label={item.label}
              value={item.key}
              onChange={(event) => {
                const idx = columns.value.findIndex(({ key }) => key === event.target.value);
                columns.value = columns.value.map((column, index) => ({
                  ...column,
                  visible: idx === index ? event.target.checked : column.visible,
                }));
              }}
            />
          ))}
        </CheckboxGroup>
        <HorizontalLayout style={{ justifyContent: 'space-between' }}>
          <Button
            theme="small"
            onClick={() => {
              columns.value = columns.value.map((column) => ({ ...column, visible: true }));
            }}
          >
            Show all
          </Button>
          <Button
            theme="small"
            onClick={() => {
              columns.value = columns.value.map((column, idx) => ({
                ...column,
                visible: DEFAULT_COLUMNS[idx].visible,
              }));
            }}
          >
            Reset
          </Button>
        </HorizontalLayout>
      </Popover>
      {/* tag::gridsnippet[] */}
      <Grid items={items.value}>
        {columns.value.map((item) => (
          <GridColumn path={item.key} hidden={!item.visible} key={item.key} />
        ))}
      </Grid>
      {/* end::gridsnippet[] */}
    </>
  );
}
popover-anchored-dialog.ts
import '@vaadin/button';
import '@vaadin/checkbox-group';
import '@vaadin/grid';
import '@vaadin/horizontal-layout';
import '@vaadin/icon';
import '@vaadin/icons';
import '@vaadin/popover';
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import type { CheckboxChangeEvent } from '@vaadin/checkbox';
import { popoverRenderer } from '@vaadin/popover/lit.js';
import { getPeople } from 'Frontend/demo/domain/DataService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
import { applyTheme } from 'Frontend/generated/theme';
const DEFAULT_COLUMNS = [
  { label: 'First name', key: 'firstName', visible: true },
  { label: 'Last name', key: 'lastName', visible: true },
  { label: 'Email', key: 'email', visible: true },
  { label: 'Phone', key: 'address.phone', visible: false },
  { label: 'Birthday', key: 'birthday', visible: false },
  { label: 'Profession', key: 'profession', visible: true },
];
@customElement('popover-anchored-dialog')
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 gridColumns = [...DEFAULT_COLUMNS];
  protected override async firstUpdated() {
    const { people } = await getPeople();
    this.items = people;
  }
  protected override render() {
    return html`
      <vaadin-horizontal-layout style="align-items: baseline">
        <h3 style="flex: 1;">Employees</h3>
        <vaadin-button id="toggle-columns" theme="icon" aria-label="Show / hide columns">
          <vaadin-icon icon="vaadin:grid-h"></vaadin-icon>
        </vaadin-button>
      </vaadin-horizontal-layout>
      <vaadin-popover
        for="toggle-columns"
        modal
        with-backdrop
        position="bottom-end"
        ${popoverRenderer(this.popoverRenderer, [this.gridColumns])}
      ></vaadin-popover>
      <!-- tag::gridsnippet[] -->
      <vaadin-grid .items="${this.items}">
        ${this.gridColumns.map(
          (column) => html`
            <vaadin-grid-column
              path="${column.key}"
              .hidden="${!column.visible}"
            ></vaadin-grid-column>
          `
        )}
      </vaadin-grid>
      <!-- end::gridsnippet[] -->
    `;
  }
  popoverRenderer() {
    const visibleColumns = this.gridColumns
      .filter((column) => column.visible)
      .map((column) => column.key);
    return html`
      <div style="font-weight: 600; padding: var(--lumo-space-xs);">Configure columns</div>
      <vaadin-checkbox-group theme="vertical" .value="${visibleColumns}">
        ${this.gridColumns.map(
          (column) => html`
            <vaadin-checkbox
              .label="${column.label}"
              .value="${column.key}"
              @change="${this.onCheckboxChange}"
            ></vaadin-checkbox>
          `
        )}
      </vaadin-checkbox-group>
      <vaadin-horizontal-layout style="justify-content: space-between;">
        <vaadin-button theme="small" @click="${this.showAllColumns}">Show all</vaadin-button>
        <vaadin-button theme="small" @click="${this.resetColumns}">Reset</vaadin-button>
      </vaadin-horizontal-layout>
    `;
  }
  onCheckboxChange(event: CheckboxChangeEvent) {
    const idx = this.gridColumns.findIndex(({ key }) => key === event.target.value);
    this.gridColumns = this.gridColumns.map((column, index) => ({
      ...column,
      visible: idx === index ? event.target.checked : column.visible,
    }));
  }
  showAllColumns() {
    this.gridColumns = this.gridColumns.map((column) => ({ ...column, visible: true }));
  }
  resetColumns() {
    this.gridColumns = this.gridColumns.map((column, idx) => ({
      ...column,
      visible: DEFAULT_COLUMNS[idx].visible,
    }));
  }
}