# Popover

A popover component for displaying contextual content anchored to a trigger

## Import

```tsx
import { Popover } from '@videojs/react';
```

## Anatomy

```tsx
<Popover.Root>
  <Popover.Trigger>Open</Popover.Trigger>
  <Popover.Popup>
    <Popover.Arrow />
    Content
  </Popover.Popup>
</Popover.Root>
```

## Behavior

Displays contextual content anchored to a trigger element. By default, opens on click and closes when clicking outside, pressing Escape, or when the trigger loses focus.

Set `openOnHover` to open on pointer hover instead of click. Use `delay` and `closeDelay` to control timing for hover interactions.

Popovers inside a [player container](https://videojs.org/docs/framework/react/reference/components/player-container) join its popup group. Opening one closes any other popover or root menu that is open in the same container. A popover outside the container still opens and closes normally, but it is not coordinated with that group.

The `side` and `align` props control the preferred popup placement relative to the trigger. When the preferred side overflows the positioning boundary, the popup uses the opposite side if it has more space.

The component is composed from four parts: `Root` manages state, `Trigger` toggles the popover, `Popup` contains the content, and `Arrow` renders a directional arrow.

## Styling

Use [CSS custom properties](#css-custom-properties) for positioning offsets:

React renders standard DOM elements. Add a `className` to style them:

```css
.popover {
  --media-popover-side-offset: 8px;
  --media-popover-align-offset: 0px;
  --media-popover-boundary-offset: 8px;
}
```

Style based on open state, rendered side, and transition phases. `data-side` reflects the rendered side and can differ from the preferred `side` prop after collision handling:

```css
.popover[data-open] .popup {
  display: block;
}
.popover[data-starting-style] .popup {
  opacity: 0;
}
.popover[data-ending-style] .popup {
  opacity: 0;
}
.popover[data-side="top"] {
  transform-origin: bottom center;
}
.popover[data-side="bottom"] {
  transform-origin: top center;
}
```

## Accessibility

The trigger receives `aria-expanded` reflecting the open state. When `modal` is set, the popup receives `aria-modal="true"`. Closing via Escape is enabled by default and can be disabled with `closeOnEscape={false}`.

## Examples

### Basic Usage

**App.tsx**

```tsx
import { Container, createPlayer, Popover } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';

const { Player } = createPlayer({ features: videoFeatures });

export default function BasicUsage() {
  return (
    <Player>
      <Container className="media-container">
        <Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
        <div className="bar">
          <Popover.Root>
            <Popover.Trigger className="trigger">Settings</Popover.Trigger>
            <Popover.Popup className="popup">
              <Popover.Arrow className="arrow" />
              <div className="content">Popover content</div>
            </Popover.Popup>
          </Popover.Root>
        </div>
      </Container>
    </Player>
  );
}
```

**App.css**

```css
.media-container {
  position: relative;
}

.media-container video {
  width: 100%;
}

.bar {
  position: absolute;
  bottom: 10px;
  left: 10px;
}

.trigger {
  padding: 6px 16px;
  color: black;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
}

.popup {
  /* Reset UA [popover] defaults */
  --media-popover-side-offset: 8px;
  padding: 12px 16px;
  margin: 0;
  font-size: 14px;
  color: white;

  background: rgba(0, 0, 0, 0.85);
  border: 0;
  border-radius: 8px;
  backdrop-filter: blur(10px);
}

.arrow {
  fill: rgba(0, 0, 0, 0.85);
}

.content {
  white-space: nowrap;
}
```

## API Reference

### Root

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `'start' \| 'center' \| 'end'` | `'center'` | Alignment of the popup along the trigger's edge. |
| `boundary` | `'viewport' \| 'container' \| string & {}` | — | Boundary used to constrain the popup position. |
| `closeDelay` | `number` | `0` | Delay in ms before closing after pointer leaves. |
| `closeOnEscape` | `boolean` | `true` | Close the popup when the Escape key is pressed. |
| `closeOnOutsideClick` | `boolean` | `true` | Close the popup when clicking outside the trigger and popup. |
| `defaultOpen` | `boolean` | `false` | Initial open state for uncontrolled usage. |
| `delay` | `number` | `300` | Delay in ms before opening on hover. |
| `modal` | `boolean \| 'trap-focus'` | `false` | - `false` (default): non-modal; background content remains interactive.<br>- `true`: modal; sets `aria-modal="true"` on the popup.<br>- `'trap-focus'`: reserved for future focus-trapping behavior. |
| `open` | `boolean` | `false` | Controlled open state. When set, the consumer is responsible for toggling. |
| `openOnHover` | `boolean` | `false` | Open the popup on pointer hover instead of click. |
| `side` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'` | Preferred side of the trigger for the popup. |

#### State

State is accessible via the `render`, `className`, and `style` props.

| Property | Type | Description |
| --- | --- | --- |
| `transitionStarting` | `boolean` | Whether the open transition is in progress. |
| `transitionEnding` | `boolean` | Whether the close transition is in progress. |
| `open` | `boolean` | |
| `status` | `'idle' \| 'starting' \| 'ending'` | |
| `side` | `'top' \| 'bottom' \| 'left' \| 'right'` | Preferred side of the trigger for the popup. |
| `align` | `'start' \| 'center' \| 'end'` | |
| `modal` | `boolean \| 'trap-focus'` | |

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-open` | — | Present when the popover is open. |
| `data-side` | `'top' \| 'bottom' \| 'left' \| 'right'` | Indicates the rendered side of the popover after collision handling. |
| `data-align` | `'start' \| 'center' \| 'end'` | Indicates how the popover is aligned relative to the specified side. |

#### CSS custom properties

| Variable | Description |
| --- | --- |
| `--media-popover-side-offset` | Distance between the popup and the trigger along the side axis. |
| `--media-popover-align-offset` | Distance between the popup and the trigger along the alignment axis. |
| `--media-popover-boundary-offset` | Minimum distance between the popup and the positioning boundary. |
| `--media-popover-anchor-width` | The anchor element's width. |
| `--media-popover-anchor-height` | The anchor element's height. |
| `--media-popover-available-width` | Available width between the trigger and the boundary edge. |
| `--media-popover-available-height` | Available height between the trigger and the boundary edge. |

### Arrow

Decorative arrow pointing from the popup toward the trigger. Hidden from assistive technology.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: PopoverState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `render` | `ReactElement \| ((props: HTMLProps, state: PopoverState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: PopoverState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

### Popup

Container for the popover content. Positioned relative to the trigger using CSS anchor positioning with a JavaScript fallback.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: PopoverState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `render` | `ReactElement \| ((props: HTMLProps, state: PopoverState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: PopoverState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-open` | — | Present when the popover is open. |
| `data-side` | `'top' \| 'bottom' \| 'left' \| 'right'` | Indicates the rendered side of the popover after collision handling. |
| `data-align` | `'start' \| 'center' \| 'end'` | Indicates how the popover is aligned relative to the specified side. |

### Trigger

Button that toggles the popover visibility. Renders a `<button>` element.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: PopoverState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `render` | `ReactElement \| ((props: HTMLProps, state: PopoverState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: PopoverState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-open` | — | Present when the popover is open. |
| `data-side` | `'top' \| 'bottom' \| 'left' \| 'right'` | Indicates the rendered side of the popover after collision handling. |
| `data-align` | `'start' \| 'center' \| 'end'` | Indicates how the popover is aligned relative to the specified side. |

---

React documentation: https://videojs.org/docs/framework/react/llms.txt
All documentation: https://videojs.org/llms.txt
