> Markdown version of [Popover](https://vaadin.com/docs/latest/components/popover). Section index: [llms.txt](https://vaadin.com/docs/latest/components/llms.txt)

# Popover

A generic overlay whose position is anchored to an element in the UI.

**Lit** — `popover-notification-panel.ts`

```html
<vaadin-popover
  for="show-notifications"
  theme="arrow no-padding"
  modal
  aria-labelledby="notifications-heading"
  width="300px"
  position="bottom"
>
  ${this.renderNotifications(this.unreadNotifications, this.allNotifications)}
</vaadin-popover>
```

**Flow** — `PopoverNotificationPanel.java`

```java
Popover popover = new Popover();
popover.setTarget(button);
popover.setWidth("300px");
popover.addThemeVariants(PopoverVariant.ARROW,
        PopoverVariant.NO_PADDING);
popover.setPosition(PopoverPosition.BOTTOM);
popover.setModal(true);
popover.setAriaLabelledBy("notifications-heading");
```

**React** — `popover-notification-panel.tsx`

```tsx
<Button id="show-notifications" aria-label="Notifications" theme="icon">
  <Icon icon="lumo:bell" />
</Button>
<Popover
  for="show-notifications"
  theme="arrow no-padding"
  modal
  position="bottom"
  width="300px"
  aria-labelledby="notifications-heading"
>
  <HorizontalLayout
    style={{
      alignItems: 'center',
      padding: 'var(--vaadin-padding-l) var(--vaadin-padding-l) var(--vaadin-padding-s)',
    }}
  >
    <h4 id="notifications-heading">Notifications</h4>
    <Button
      slot="end"
      theme="small"
      onClick={() => {
        unreadNotifications.value = [];
      }}
    >
      Mark all read
    </Button>
  </HorizontalLayout>
  <TabSheet theme="small no-padding no-border" className="notifications">
    <TabSheetTab label="Unread">
      {unreadNotifications.value.length ? (
        <MessageList items={unreadNotifications.value} />
      ) : (
        <div className="no-notifications-msg">No new notifications</div>
      )}
    </TabSheetTab>
    <TabSheetTab label="All">
      <MessageList items={allNotifications.value} />
    </TabSheetTab>
  </TabSheet>
</Popover>
```

`popover-notification-panel.css`

```css
#notifications-heading {
  font-size: 1.125rem;
}

vaadin-tabsheet.notifications {
  max-height: 350px;

  & vaadin-message-list {
    & vaadin-message {
      padding: var(--vaadin-padding-s) 0 var(--vaadin-padding-l);
      margin: 0 var(--vaadin-padding-l);
      font-size: 0.875rem;
      border-bottom: 1px solid var(--vaadin-border-color-secondary);

      &::part(name) {
        font-weight: 600;
        margin-right: auto;
      }
      &::part(time) {
        font-size: 0.75rem;
      }
      &::part(message) {
        font-size: 0.875rem;
        line-height: 1.25;
        color: var(--vaadin-text-color-secondary);
      }
    }
  }

  .no-notifications-msg {
    padding: var(--vaadin-padding-l);
    color: var(--vaadin-text-color-secondary);
  }
}
```

Popovers support focusable, interactive content, and can be used to build virtually any type of anchored overlays from custom drop-down fields and drop-down buttons to interactive tooltips.

The popover’s position is anchored to an element in the UI, called the **target element**, by calling `setTarget()`. The actual content of the popover is added to it as with any other component container.

Popovers differ from [Dialogs](https://vaadin.com/docs/latest/components/dialog.md) in that they are visually anchored to a target element, and they differ from [Tooltips](https://vaadin.com/docs/latest/components/tooltip.md) in that they can be focused and support rich, interactive content.

## <a id="opening-and-closing"></a>Opening and Closing

Popovers can be configured to open and close based on different pointer and keyboard based triggers. See [Typical Use Cases](#typical-use-cases) for examples.

### <a id="opening-triggers"></a>Opening Triggers

Three target element triggers can be configured to open the popover:

- **Click**: clicking the target element, or pressing `Space` when the target element has focus (**default**)

- **Hover**: hovering over the target element

- **Focus**: focusing the target element

**Flow**

```java
Popover popover = new Popover();
popover.setOpenOnClick(false);
popover.setOpenOnHover(true);
popover.setOpenOnFocus(true);
```

**Lit**

```typescript
<vaadin-popover .trigger="${['hover', 'focus']}"></vaadin-popover>
```

**React**

```tsx
<Popover trigger={['hover', 'focus']} />
```

All three triggers can be enabled simultaneously. If no opening trigger is enabled, the popover can only be opened programmatically.

The hover and focus opening triggers have a configurable opening delay.

**Flow**

```java
Popover popover = new Popover();
popover.setHoverDelay(500);
popover.setFocusDelay(500);
```

**Lit**

```html
<vaadin-popover hover-delay="500" focus-delay="500"></vaadin-popover>
```

**React**

```tsx
<Popover hoverDelay={500} focusDelay={500} />
```

### <a id="closing-triggers"></a>Closing Triggers

The following triggers close the popover by default:

- **Target click**: Clicking the target element (non-modal popovers only)

- **Outside click**: clicking anywhere outside the popover (can be disabled)

- **Esc**: pressing `Esc` (can be disabled)

**Flow**

```java
Popover popover = new Popover();
popover.setCloseOnEsc(false);
popover.setCloseOnOutsideClick(false);
```

**Lit**

```html
<vaadin-popover no-close-on-esc no-close-on-outside-click></vaadin-popover>
```

**React**

```tsx
<Popover noCloseOnEsc noCloseOnOutsideClick />
```

Additionally:

- When opened on hover, the popover closes on mouseout, i.e. when the pointer leaves the target element and the popover.

- When opened on focus, the popover closes on blur, i.e. when focus is no longer on the target element or in the popover.

The mouseout closing trigger has a configurable delay.

**Flow**

```java
Popover popover = new Popover();
popover.setHideDelay(500);
```

**Lit**

```html
<vaadin-popover hide-delay="500"></vaadin-popover>
```

**React**

```tsx
<Popover hideDelay={500} />
```

### <a id="auto-focus"></a>Auto Focus

Keyboard focus can be automatically moved to the Popover when it opens. This is recommended for popovers with interactive content that the user is expected to interact with. Modal popovers have auto-focus behavior by default. Popovers opened with hover or focus should not use auto-focus.

**Flow**

```java
Popover popover = new Popover();
popover.setAutofocus(true);
```

**Lit**

```html
<vaadin-popover autofocus></vaadin-popover>
```

**React**

```tsx
<Popover autofocus />
```

### <a id="tab-focus"></a>Tab Focus (since V25.2)

By default, the popover’s content can be reached with `Tab` from its target element. This can be disabled, so that pressing `Tab` on the target skips past the popover, and `Shift`+`Tab` doesn’t move focus into the popover’s last focusable element. Focus placed inside the popover programmatically still moves out of it on `Tab` and `Shift`+`Tab`.

This setting has no effect on modal popovers, which use their own focus trap.

**Flow**

```java
Popover popover = new Popover();
popover.setTabFocusEnabled(false);
```

**Lit**

```html
<vaadin-popover no-tab-focus></vaadin-popover>
```

**React**

```tsx
<Popover noTabFocus />
```

## <a id="positioning"></a>Positioning

By default, popovers open below their target element, horizontally centered to its midpoint, but the positioning options allow this to be changed to any edge or corner, depending on what is most appropriate for the use case.

The popover’s position is automatically maintained if the target element scrolls within the viewport. If there is insufficient space in the viewport for the desired positioning, the popover automatically shifts to fit within the viewport.

**Lit** — `popover-positioning.ts`

```html
<vaadin-popover for="target" .position="${this.position}">Popover content</vaadin-popover>
```

**Flow** — `PopoverPositioning.java`

```java
select.addValueChangeListener(event -> {
    PopoverPosition position = event.getValue();
    popover.setPosition(position);
});
```

**React** — `popover-positioning.tsx`

```tsx
<Popover for="target" position={position.value}>
  Popover content
</Popover>
```

### <a id="target-gap"></a>Target Gap

The distance between the popover and the target element can be customized by setting the following CSS properties on the Popover component:

- `--vaadin-popover-offset-top`

- `--vaadin-popover-offset-bottom`

- `--vaadin-popover-offset-start`

- `--vaadin-popover-offset-end`

### <a id="target-arrow"></a>Target Arrow

Popovers can render a wedge-shaped arrow tip pointing at the target element.

**Lit** — `popover-arrow.ts`

```html
<vaadin-popover for="target" theme="arrow">
  <div>No new notifications</div>
</vaadin-popover>
```

**Flow** — `PopoverArrow.java`

```java
Popover popover = new Popover();
popover.addThemeVariants(PopoverVariant.ARROW);
```

**React** — `popover-arrow.tsx`

```tsx
return (
  <>
    <Button id="target" aria-label="Notifications" theme="icon">
      <Icon icon="lumo:bell" />
    </Button>
    <Popover for="target" theme="arrow">
      <div>No new notifications</div>
    </Popover>
  </>
);
```

## <a id="configuring-delays"></a>Configuring Delays

The delay before popover opens can be configured separately for hover and focus. There is no delay before the popover appears on click or when opening programmatically.

The delay before popover closes — when the pointer leaves the target element — can also be configured separately. On blur, though, the popover is closed immediately to avoid confusion when focusing another element.

```java
// Global delay configuration:
Popover.setDefaultFocusDelay(2000);
Popover.setDefaultHoverDelay(1000);
Popover.setDefaultHideDelay(1000);

// Overriding delays for a particular popover:
Popover popover = new Popover();
popover.setHoverDelay(0);
```

```typescript
import { Popover } from '@vaadin/popover';

// Global delay configuration:
Popover.setDefaultFocusDelay(2000);
Popover.setDefaultHoverDelay(1000);
Popover.setDefaultHideDelay(1000);

// Overriding delays for a particular popover:
<vaadin-popover .hoverDelay=${500}></vaadin-popover>
```

```tsx
import { Popover } from '@vaadin/popover';

// Global delay configuration:
Popover.setDefaultFocusDelay(2000);
Popover.setDefaultHoverDelay(1000);
Popover.setDefaultHideDelay(1000);

// Overriding delays for a particular popover:
<Popover hoverDelay={500} />
```

## <a id="dimensions"></a>Dimensions

By default, the Popover’s size is determined by its contents, but an explicit width and height can be set on the Popover itself.

Contents that exceed the width of the popover will scroll.

The maximum width of popovers is limited to the width of the viewport, minus a small margin that can be customized by the theme.

## <a id="modality"></a>Modality

A modal Popover blocks the user from interacting with the rest of the user interface while open, automatically moves focus from the target element to the Popover, and traps keyboard focus within it.

When combined with an outside click closing trigger, modality prevents accidentally triggering other UI elements when clicking outside the Popover to close it.

By default, modal popovers do not render a modality curtain (or *backdrop*), but one can be enabled separately. A modality curtain can be useful for de-emphasizing the UI in the background and to give a visual indication that the rest of the UI is blocked from user interaction.

**Lit** — `popover-modal.ts`

```html
<vaadin-popover for="target" modal with-backdrop>
  <vaadin-text-field label="Discount code"></vaadin-text-field>
  <vaadin-button>Apply</vaadin-button>
</vaadin-popover>
```

**Flow** — `PopoverModal.java`

```java
Popover popover = new Popover();
popover.setModal(true);
popover.setBackdropVisible(true);
```

**React** — `popover-modal.tsx`

```tsx
<Popover for="target" modal withBackdrop>
  <TextField label="Discount code" />
  <Button>Apply</Button>
</Popover>
```

## <a id="accessibility"></a>Accessibility

By default, the Popover has the ARIA role `dialog`. This can be changed to another role to provide appropriate semantics for the type of content and interaction in the popover (see [Typical Use Cases](#typical-use-cases) for examples).

- `menu`: when the content is a list of actions or links

- `listbox`: when the content is a list form which you can select one or more items

- `grid`: when the content is a tabular structure from which you select an item

- `tree`: when the content is a hierarchical list

Remember that, unlike [Tooltip](https://vaadin.com/docs/latest/components/tooltip.md), the contents of a Popover are *not* automatically announced by screen readers when it opens. Consider using a live region to announce non-interactive popovers, and ensure keyboard access to interactive popovers.

The target element is automatically applied `aria-controls` (with a reference to the Popover), `aria-haspopup` (with the overlay’s role as the value), and `aria-expanded` (set to `true` when open, and to `false` otherwise).

An accessible name can be provided for the popover using the ARIA label API:

**Flow**

```java
Popover popover = new Popover();
popover.setAriaLabel("Label");
// OR
popover.setAriaLabelledby("label-element-id");
```

**Lit**

```html
<vaadin-popover aria-label="Label"></vaadin-popover>
<vaadin-popover aria-labelledby="label-element-id"></vaadin-popover>
```

**React**

```tsx
<Popover aria-label="Label" />
<Popover aria-labelledby="label-element-id" />
```

## <a id="typical-use-cases"></a>Typical Use Cases

Here are a few examples of common use cases for the Popover component with recommended configurations.

### <a id="drop-down-field"></a>Drop-Down Field

**Lit** — `popover-dropdown-field.ts`

```html
<vaadin-popover
  for="range-field"
  modal
  width="340px"
  position="bottom-start"
  aria-label="Select a date range"
  .opened="${this.opened}"
  @closed="${this.onClosed}"
>
  <vaadin-select
    label="Common ranges"
    .items="${this.presets}"
    placeholder="Select preset"
    style="width: 100%"
    .value="${this.range}"
    @change="${this.onRangeChange}"
  ></vaadin-select>
  <vaadin-horizontal-layout style="align-items: baseline; gap: var(--vaadin-gap-s);">
    <vaadin-date-picker
      label="From"
      style="width: 150px"
      .value="${this.from}"
      @change="${this.onFromChange}"
    ></vaadin-date-picker>
    <div>−</div>
    <vaadin-date-picker
      label="To"
      style="width: 150px"
      .value="${this.to}"
      @change="${this.onToChange}"
    ></vaadin-date-picker>
  </vaadin-horizontal-layout>
</vaadin-popover>
```

**Flow** — `PopoverDropdownField.java`

```java
TextField field = new TextField("Search date range");
field.setWidth("340px");
Icon icon = LumoIcon.DROPDOWN.create();
field.setSuffixComponent(icon);

popover = new Popover();
popover.setModal(true);
popover.setWidth("340px");
popover.setAriaLabel("Select a date range");
popover.setTarget(field);

Shortcuts.addShortcutListener(field, popover::open, Key.ARROW_DOWN)
        .listenOn(field);
```

**React** — `popover-dropdown-field.tsx`

```tsx
<Popover
  for="range-field"
  modal
  width="340px"
  position="bottom-start"
  aria-label="Select a date range"
  opened={opened.value}
  onOpenedChanged={(e) => {
    if (e.detail.value) {
      opened.value = true;
    }
  }}
  onClosed={() => {
    opened.value = false;
  }}
>
  <Select
    label="Common ranges"
    placeholder="Select preset"
    items={presets}
    value={range.value}
    style={{ width: '100%' }}
    onChange={onPresetChange}
  />
  <HorizontalLayout style={{ alignItems: 'baseline', gap: 'var(--vaadin-gap-s)' }}>
    <DatePicker
      label="From"
      value={from.value}
      style={{ width: '150px' }}
      onChange={(event) => {
        range.value = '';
        from.value = event.target.value;
      }}
    />
    <div>−</div>
    <DatePicker
      label="To"
      value={to.value}
      style={{ width: '150px' }}
      onChange={(event) => {
        range.value = '';
        to.value = event.target.value;
      }}
    />
  </HorizontalLayout>
</Popover>
```

- Opens on click, focus

- Closes on `Esc`, blur, outside click, target click, and programmatically upon selection

- Modal, no modality curtain (drop-down fields typically don’t have curtains)

- Auto-focused

- ARIA role `dialog`

- ARIA label *“Select a date range”*

### <a id="user-menu"></a>User Menu

**Lit** — `popover-user-menu.ts`

```html
<vaadin-popover
  for="avatar"
  position="bottom-end"
  modal
  role="menu"
  aria-label="User menu"
  theme="no-padding"
>
  ${this.renderUserMenu(this.person)}
</vaadin-popover>
```

**Flow** — `PopoverUserMenu.java`

```java
String name = person.getFirstName() + " " + person.getLastName();
String pictureUrl = person.getPictureUrl();

Avatar avatar = new Avatar(name);
avatar.setImage(pictureUrl);
avatar.getStyle().set("display", "block");
avatar.getStyle().set("cursor", "var(--vaadin-clickable-cursor)");
avatar.getElement().setAttribute("tabindex", "-1");

Button button = new Button(avatar);
button.addThemeVariants(ButtonVariant.LUMO_ICON,
        ButtonVariant.TERTIARY);
button.getStyle().set("margin", "var(--vaadin-gap-s)");
button.getStyle().set("margin-inline-start", "auto");
button.getStyle().set("padding", "0");
button.getStyle().set("border-radius", "50%");

Popover popover = new Popover();
popover.setModal(true);
popover.setAriaRole("menu");
popover.setAriaLabel("User menu");
popover.setTarget(button);
popover.setPosition(PopoverPosition.BOTTOM_END);
popover.addThemeVariants(PopoverVariant.NO_PADDING);
```

**React** — `popover-user-menu.tsx`

```tsx
<Popover
  for="avatar"
  position="bottom-end"
  role="menu"
  modal
  aria-label="User menu"
  theme="no-padding"
>
  <div className="person-item" style={{ padding: 'var(--vaadin-padding-s)' }}>
    <Avatar img={pictureUrl} name={`${firstName} ${lastName}`} />
    <span>
      {firstName} {lastName}
    </span>
    <span>{nickName}</span>
  </div>
  <VerticalLayout className="userMenuLinks" style={{ alignItems: 'stretch', width: '100%' }}>
    <a href="#" role="menuitem">
      User profile
    </a>
    <a href="#" role="menuitem">
      Preferences
    </a>
    <a href="#" role="menuitem">
      Sign out
    </a>
  </VerticalLayout>
</Popover>
```

`popover-user-menu.css`

```css
.userMenuLinks {
  padding-bottom: var(--vaadin-padding-xs);
  border-top: 1px solid var(--vaadin-border-color-secondary);

  & a {
    padding: var(--vaadin-padding-xs) var(--vaadin-padding-m);
    color: var(--vaadin-text-color);
    text-decoration: none;

    &:hover {
      background: var(--vaadin-background-container);
    }
  }
}
```

`person-item.css`

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

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

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

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

- Opens on click

- Closes on `Esc`, outside click, target click

- Modal, no modality curtain (small popovers like this usually don’t need them)

- ARIA role `menu` (the overlay’s content is a menu; although there are non-list elements, they are not interactive, nor do they need to be announced by screen readers)

- ARIA label *“User menu”*

Note: if the popover only needs to contain menu items, consider using a [Menu Bar](https://vaadin.com/docs/latest/components/menu-bar.md) or [Context Menu](https://vaadin.com/docs/latest/components/context-menu.md) instead.

### <a id="notification-panel"></a>Notification Panel

**Lit** — `popover-notification-panel.ts`

```html
<vaadin-popover
  for="show-notifications"
  theme="arrow no-padding"
  modal
  aria-labelledby="notifications-heading"
  width="300px"
  position="bottom"
>
  ${this.renderNotifications(this.unreadNotifications, this.allNotifications)}
</vaadin-popover>
```

**Flow** — `PopoverNotificationPanel.java`

```java
Popover popover = new Popover();
popover.setTarget(button);
popover.setWidth("300px");
popover.addThemeVariants(PopoverVariant.ARROW,
        PopoverVariant.NO_PADDING);
popover.setPosition(PopoverPosition.BOTTOM);
popover.setModal(true);
popover.setAriaLabelledBy("notifications-heading");
```

**React** — `popover-notification-panel.tsx`

```tsx
<Button id="show-notifications" aria-label="Notifications" theme="icon">
  <Icon icon="lumo:bell" />
</Button>
<Popover
  for="show-notifications"
  theme="arrow no-padding"
  modal
  position="bottom"
  width="300px"
  aria-labelledby="notifications-heading"
>
  <HorizontalLayout
    style={{
      alignItems: 'center',
      padding: 'var(--vaadin-padding-l) var(--vaadin-padding-l) var(--vaadin-padding-s)',
    }}
  >
    <h4 id="notifications-heading">Notifications</h4>
    <Button
      slot="end"
      theme="small"
      onClick={() => {
        unreadNotifications.value = [];
      }}
    >
      Mark all read
    </Button>
  </HorizontalLayout>
  <TabSheet theme="small no-padding no-border" className="notifications">
    <TabSheetTab label="Unread">
      {unreadNotifications.value.length ? (
        <MessageList items={unreadNotifications.value} />
      ) : (
        <div className="no-notifications-msg">No new notifications</div>
      )}
    </TabSheetTab>
    <TabSheetTab label="All">
      <MessageList items={allNotifications.value} />
    </TabSheetTab>
  </TabSheet>
</Popover>
```

`popover-notification-panel.css`

```css
#notifications-heading {
  font-size: 1.125rem;
}

vaadin-tabsheet.notifications {
  max-height: 350px;

  & vaadin-message-list {
    & vaadin-message {
      padding: var(--vaadin-padding-s) 0 var(--vaadin-padding-l);
      margin: 0 var(--vaadin-padding-l);
      font-size: 0.875rem;
      border-bottom: 1px solid var(--vaadin-border-color-secondary);

      &::part(name) {
        font-weight: 600;
        margin-right: auto;
      }
      &::part(time) {
        font-size: 0.75rem;
      }
      &::part(message) {
        font-size: 0.875rem;
        line-height: 1.25;
        color: var(--vaadin-text-color-secondary);
      }
    }
  }

  .no-notifications-msg {
    padding: var(--vaadin-padding-l);
    color: var(--vaadin-text-color-secondary);
  }
}
```

- Opens on click

- Closes on `Esc`, outside click, target click

- Modal, no modality curtain (small popovers like this usually don’t need them)

- ARIA role `dialog` (although mainly a list, there interactive non-list elements as well)

- ARIA labelled-by pointing to heading in the popover

- Arrow variant

### <a id="rich-interactive-tooltip"></a>Rich, Interactive Tooltip

**Lit** — `popover-interactive-tooltip.ts`

```html
<vaadin-integer-field id="cvv-field" label="CVV" style="width: 60px"></vaadin-integer-field>
<vaadin-popover
  for="cvv-field"
  .trigger="${this.trigger}"
  position="top"
  theme="arrow"
  aria-labelledby="cvv-heading"
>
  <h3 id="cvv-heading" style="margin: 0; font-size: 1rem">Card Verification Value</h3>
  <div style="max-width: 300px">
    A three or four digit code, usually printed on the back of the card, next to, or at the
    end of, the signature strip.
  </div>
  <a href="https://www.cvvnumber.com/cvv.html" target="_blank">
    See where to find CVV on different cards
  </a>
</vaadin-popover>
```

**Flow** — `PopoverInteractiveTooltip.java`

```java
IntegerField cvv = new IntegerField("CVV");
cvv.setWidth("60px");

Popover popover = new Popover();
popover.addThemeVariants(PopoverVariant.ARROW);
popover.setPosition(PopoverPosition.TOP);
popover.setOpenOnClick(false);
popover.setOpenOnHover(true);
popover.setOpenOnFocus(true);

H3 header = new H3("Card Verification Value");
header.setId("cvv-heading");
header.getStyle().set("margin", "0");
header.getStyle().set("font-size", "1rem");

Div content = new Div(
        "A three or four digit code, usually printed on the back of the card, "
                + "next to, or at the end of, the signature strip.");
content.getStyle().set("max-width", "300px");

Anchor link = new Anchor("https://www.cvvnumber.com/cvv.html",
        "See where to find CVV on different cards", AnchorTarget.BLANK);

popover.add(header, content, link);
popover.setAriaLabelledBy("cvv-heading");
popover.setTarget(cvv);

add(cvv, popover);
```

**React** — `popover-interactive-tooltip.tsx`

```tsx
return (
  <>
    <IntegerField id="cvv-field" label="CVV" style={{ width: '60px' }} />
    <Popover
      for="cvv-field"
      position="top"
      trigger={['hover', 'focus']}
      aria-labelledby="cvv-heading"
      theme="arrow"
    >
      <h3 id="cvv-heading" style={{ margin: 0, fontSize: '1rem' }}>
        Card Verification Value
      </h3>
      <div style={{ maxWidth: '300px' }}>
        A three or four digit code, usually printed on the back of the card, next to, or at the
        end of, the signature strip.
      </div>
      <a href="https://www.cvvnumber.com/cvv.html" target="_blank">
        See where to find CVV on different cards
      </a>
    </Popover>
  </>
);
```

- Opens on hover, focus

- Closes on `Esc`, outside click (as well as mouseout and blur)

- Non-modal

- Not auto-focused

- ARIA role `dialog` (`tooltip` would be invalid due to interactive contents)

- ARIA labelled-by pointing to heading in the popover

- Positioned above target

- Arrow variant

### <a id="anchored-modal-dialog"></a>Anchored Modal Dialog

**Lit** — `popover-anchored-dialog.ts`

```html
<vaadin-horizontal-layout style="align-items: baseline">
      <h3>Employees</h3>
      <vaadin-button id="toggle-columns" slot="end" 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">
      ${this.renderPopover(this.gridColumns)}
    </vaadin-popover>
renderPopover(columns: ColumnConfig[]) {
  const visibleColumns = columns.filter((column) => column.visible).map((column) => column.key);

  return html`
    <div style="font-weight: 600;">Configure columns</div>
    <vaadin-checkbox-group
      theme="vertical"
      .value="${visibleColumns}"
      style="padding: 0; margin: var(--vaadin-gap-s) 0;"
    >
      ${columns.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; gap: var(--vaadin-gap-s);">
      <vaadin-button theme="small" @click="${this.showAllColumns}">Show all</vaadin-button>
      <vaadin-button theme="small" @click="${this.resetColumns}">Reset</vaadin-button>
    </vaadin-horizontal-layout>
  `;
}
```

**Flow** — `PopoverAnchoredDialog.java`

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

List<String> columns = List.of("firstName", "lastName", "email",
        "phone", "birthday", "profession");

CheckboxGroup<String> group = new CheckboxGroup<>();
group.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
group.getStyle().set("padding", "0").set("margin",
        "var(--vaadin-gap-s) 0");
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));
    });
});

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.SMALL);

Button reset = new Button("Reset", (e) -> {
    group.setValue(defaultColumns);
});
reset.addThemeVariants(ButtonVariant.SMALL);

HorizontalLayout footer = new HorizontalLayout(showAll, reset);
footer.setSpacing("var(--vaadin-gap-xs)");
footer.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);

popover.add(heading, group, footer);
```

**React** — `popover-anchored-dialog.tsx`

```tsx
<Popover for="toggle-columns" modal withBackdrop position="bottom-end">
  <div style={{ fontWeight: '600' }}>Configure columns</div>
  <CheckboxGroup
    theme="vertical"
    value={visibleColumns.value}
    style={{ padding: '0', margin: 'var(--vaadin-gap-s) 0' }}
  >
    {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', gap: 'var(--vaadin-gap-s)' }}>
    <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>
```

- Opens on click

- Closes on `Esc`, outside click

- Modal, with curtain

- Auto-focused

- ARIA role `dialog`

- ARIA labelled-by pointing to heading in the popover

Note: if the dialog doesn’t benefit from being anchored-positioned to another element, consider using a [Dialog](https://vaadin.com/docs/latest/components/dialog.md) instead.

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

| Component                                                                 | Usage Recommendation                                                                                                                                                                                                                                                                           |
| ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Dialog](https://vaadin.com/docs/latest/components/dialog.md)             | Use instead of Popover if there is no need to visually associate it with another UI element. Modal dialogs are a better option for transactional operations. Modal dialogs provide full-stack modality (e.g. keyboard shortcuts defined in the UI behind are blocked while the Dialog is open) |
| [Tooltip](https://vaadin.com/docs/latest/components/tooltip.md)           | Use instead of Popover if the content is just text.                                                                                                                                                                                                                                            |
| [Context Menu](https://vaadin.com/docs/latest/components/context-menu.md) | Use instead of Popover if the content is a list of actions or toggles.                                                                                                                                                                                                                         |
| [Menu Bar](https://vaadin.com/docs/latest/components/menu-bar.md)         | Same as Context Menu, but with built-in trigger buttons.                                                                                                                                                                                                                                       |
