# Menu

A composable menu component for settings, option selection, and actions

## Import

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

## Anatomy

A root menu owns one positioned Popup. Nested `Content` elements share that Popup with the root Content.

```tsx
<Menu.Root>
  <Menu.Trigger />
  <Menu.Popup>
    <Menu.Content>
      {/* Parent menu item that opens a submenu */}
      <Menu.Root>
        <Menu.Trigger />

        <Menu.Content>
          <Menu.Item>Back</Menu.Item>
          <Menu.RadioGroup>
            <Menu.GroupLabel />
            <Menu.RadioItem>
              <Menu.ItemIndicator />
            </Menu.RadioItem>
          </Menu.RadioGroup>
        </Menu.Content>
      </Menu.Root>

      {/* Other parent menu items */}
      <Menu.Group>
        <Menu.GroupLabel />
        <Menu.CheckboxItem>
          <Menu.ItemIndicator />
        </Menu.CheckboxItem>
      </Menu.Group>
      <Menu.Separator />
      <Menu.Item />
    </Menu.Content>
  </Menu.Popup>
</Menu.Root>
```

## Behavior

Menus open from a trigger and close when you select an item, click outside, move focus away, or press Escape. Root menus use `side` and `align` as their preferred placement. When the preferred side overflows the positioning boundary, the menu uses the opposite side if it has more space.

Root menus inside a [player container](https://videojs.org/docs/framework/react/reference/components/player-container) join its popup group. Opening one closes any other root menu or popover that is open in the same container. Submenus remain part of their root menu instead of registering separately.

Create a submenu by nesting another `Menu.Root` in the parent `Menu.Content`; its `Menu.Trigger` behaves as a parent item and its `Menu.Content` becomes the active Content.

Media option groups share their option state with the enclosing menu: the selected value, and whether the options are disabled, hidden, or available. The menu’s trigger inherits the disabled and hidden state and exposes `data-availability`, and the menu closes if its options disappear while open. See [PlaybackRateRadioGroup](https://videojs.org/docs/framework/react/reference/components/playback-rate-radio-group), [QualityRadioGroup](https://videojs.org/docs/framework/react/reference/components/quality-radio-group), [AudioTrackRadioGroup](https://videojs.org/docs/framework/react/reference/components/audio-track-radio-group), and [CaptionsRadioGroup](https://videojs.org/docs/framework/react/reference/components/captions-radio-group).

Wrap a submenu’s `Menu.Trigger` and `Menu.Content` in the group’s `Root` part, and render its `Value` part inside the trigger to show the current selection:

```tsx
<Menu.Root>
  <QualityRadioGroup.Root>
    <Menu.Trigger>
      Quality
      <QualityRadioGroup.Value />
    </Menu.Trigger>
    <Menu.Content>
      <QualityRadioGroup.Options renderItem={...} />
    </Menu.Content>
  </QualityRadioGroup.Root>
</Menu.Root>
```

## Styling

Use data attributes to style open state, highlighted items, selected radio items, and submenu views:

`data-highlighted` identifies the current menu item. Its value is `"pointer"` when pointer movement caused the highlight and an empty string for other highlights, including keyboard navigation and type-ahead search. Use `[data-highlighted]` to match any highlighted item or `[data-highlighted=""]` to match highlights that were not caused by pointer movement.

The root `Menu.Popup` receives `data-open`, `data-side`, and `data-align`. Each `Menu.Content` receives menu state such as `data-open` and `data-submenu`; a Content with an open logical child receives `data-child-open`.

Every navigable item receives `data-item`. Use `[data-item]` when one rule should target regular, radio, checkbox, and submenu-trigger items together.

```css
.menu-popup[data-open] {
  opacity: 1;
}

.menu-content[data-child-open] {
  translate: -100% 0;
}

.menu-item[data-highlighted] {
  background: rgba(255, 255, 255, 0.16);
  transition: background-color 100ms ease-in-out;
}

.menu-item[data-highlighted=""] {
  transition-duration: 0ms;
}

[role="menuitemradio"][aria-checked="true"] {
  font-weight: 600;
}
```

## Accessibility

Menu content renders with `role="menu"`. Items use `menuitem`, `menuitemradio`, or `menuitemcheckbox` roles. Radio and checkbox items reflect selection with `aria-checked`.

Keyboard controls:

- Enter / Space: Select the highlighted item.
- Arrow Up / Arrow Down: Move between items.
- Arrow Right: Open a submenu.
- Arrow Left: Return to the parent menu.
- Escape: Close the root menu or return from a submenu.

Use `Menu.GroupLabel` inside grouped choices so the group receives an accessible label.

## Examples

### Basic usage

**App.tsx**

```tsx
import {
  Container,
  createPlayer,
  Menu,
  useAudioTrackOptions,
  useCaptionsOptions,
  usePlaybackRateOptions,
  useQualityOptions,
} from '@videojs/react';
import { HlsJsVideo } from '@videojs/react/media/hlsjs-video';
import { videoFeatures } from '@videojs/react/video';
import type { ReactNode } from 'react';

const { Player } = createPlayer({ features: videoFeatures });
const src = 'https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8';

function SettingsMenu(): ReactNode {
  const playbackRate = usePlaybackRateOptions();
  const quality = useQualityOptions();
  const audioTrack = useAudioTrackOptions();
  const captions = useCaptionsOptions();
  const hasPlaybackRate = playbackRate?.state.availability === 'available';
  const hasQuality = quality?.state.availability === 'available';
  const hasAudioTrack = audioTrack?.state.availability === 'available';
  const hasCaptions = captions?.state.availability === 'available';
  if (!hasPlaybackRate && !hasQuality && !hasAudioTrack && !hasCaptions) return null;

  return (
    <Menu.Root side="top" align="end">
      <Menu.Trigger className="settings-trigger" aria-label="Settings" render={<button type="button" />}>
        Settings
      </Menu.Trigger>
      <Menu.Popup className="menu">
        <Menu.Content className="menu-content">
          {hasQuality ? (
            <Menu.Root>
              <Menu.Trigger
                className="menu-item"
                render={(props) => (
                  <div {...props}>
                    <span>Quality</span>
                    <span className="menu-value">
                      {quality.selectedLabel}
                      <span aria-hidden="true">›</span>
                    </span>
                  </div>
                )}
              />
              <Menu.Content className="menu-panel">
                <Menu.Item className="menu-back">
                  <span aria-hidden="true">‹</span>
                  Quality
                </Menu.Item>
                <Menu.RadioGroup
                  className="menu-group"
                  value={quality.value}
                  onValueChange={quality.setValue}
                  aria-label="Quality"
                >
                  {quality.options.map((option) => (
                    <Menu.RadioItem
                      key={option.value}
                      value={option.value}
                      disabled={option.disabled}
                      className="menu-item"
                    >
                      <span>
                        {option.label}
                        {option.tier ? <sup className="menu-tier">{option.tier}</sup> : null}
                      </span>
                      {option.badge ? <span className="menu-badge">{option.badge}</span> : null}
                      <Menu.ItemIndicator
                        checked={option.value === quality.value}
                        forceMount
                        className="menu-indicator"
                      >
                        ✓
                      </Menu.ItemIndicator>
                    </Menu.RadioItem>
                  ))}
                </Menu.RadioGroup>
              </Menu.Content>
            </Menu.Root>
          ) : null}

          {hasAudioTrack ? (
            <Menu.Root>
              <Menu.Trigger
                className="menu-item"
                render={(props) => (
                  <div {...props}>
                    <span>Audio</span>
                    <span className="menu-value">
                      {audioTrack.selectedLabel}
                      <span aria-hidden="true">›</span>
                    </span>
                  </div>
                )}
              />
              <Menu.Content className="menu-panel">
                <Menu.Item className="menu-back">
                  <span aria-hidden="true">‹</span>
                  Audio
                </Menu.Item>
                <Menu.RadioGroup
                  className="menu-group"
                  value={audioTrack.value}
                  onValueChange={audioTrack.setValue}
                  aria-label="Audio tracks"
                >
                  {audioTrack.options.map((option) => (
                    <Menu.RadioItem
                      key={option.value}
                      value={option.value}
                      disabled={option.disabled}
                      className="menu-item"
                    >
                      <span>{option.label}</span>
                      <Menu.ItemIndicator
                        checked={option.value === audioTrack.value}
                        forceMount
                        className="menu-indicator"
                      >
                        ✓
                      </Menu.ItemIndicator>
                    </Menu.RadioItem>
                  ))}
                </Menu.RadioGroup>
              </Menu.Content>
            </Menu.Root>
          ) : null}

          {hasPlaybackRate ? (
            <Menu.Root>
              <Menu.Trigger
                className="menu-item"
                render={(props) => (
                  <div {...props}>
                    <span>Speed</span>
                    <span className="menu-value">
                      {playbackRate.selectedLabel}
                      <span aria-hidden="true">›</span>
                    </span>
                  </div>
                )}
              />
              <Menu.Content className="menu-panel">
                <Menu.Item className="menu-back">
                  <span aria-hidden="true">‹</span>
                  Speed
                </Menu.Item>
                <Menu.RadioGroup
                  className="menu-group"
                  value={playbackRate.value}
                  onValueChange={playbackRate.setValue}
                  aria-label="Playback rate"
                >
                  {playbackRate.options.map((option) => (
                    <Menu.RadioItem
                      key={option.value}
                      value={option.value}
                      disabled={option.disabled}
                      className="menu-item"
                    >
                      <span>{option.label}</span>
                      <Menu.ItemIndicator
                        checked={option.value === playbackRate.value}
                        forceMount
                        className="menu-indicator"
                      >
                        ✓
                      </Menu.ItemIndicator>
                    </Menu.RadioItem>
                  ))}
                </Menu.RadioGroup>
              </Menu.Content>
            </Menu.Root>
          ) : null}

          {hasCaptions ? (
            <Menu.Root>
              <Menu.Trigger
                className="menu-item"
                render={(props) => (
                  <div {...props}>
                    <span>Captions</span>
                    <span className="menu-value">
                      {captions.selectedLabel}
                      <span aria-hidden="true">›</span>
                    </span>
                  </div>
                )}
              />
              <Menu.Content className="menu-panel">
                <Menu.Item className="menu-back">
                  <span aria-hidden="true">‹</span>
                  Captions
                </Menu.Item>
                <Menu.RadioGroup
                  className="menu-group"
                  value={captions.value}
                  onValueChange={captions.setValue}
                  aria-label="Captions"
                >
                  {captions.options.map((option) => (
                    <Menu.RadioItem
                      key={option.value}
                      value={option.value}
                      disabled={option.disabled}
                      className="menu-item"
                    >
                      <span>{option.label}</span>
                      <Menu.ItemIndicator
                        checked={option.value === captions.value}
                        forceMount
                        className="menu-indicator"
                      >
                        ✓
                      </Menu.ItemIndicator>
                    </Menu.RadioItem>
                  ))}
                </Menu.RadioGroup>
              </Menu.Content>
            </Menu.Root>
          ) : null}

          <Menu.Item className="menu-item" onSelect={() => navigator.clipboard?.writeText(window.location.href)}>
            Copy link
          </Menu.Item>
        </Menu.Content>
      </Menu.Popup>
    </Menu.Root>
  );
}

export default function BasicUsage() {
  return (
    <Player>
      <Container className="media-container">
        <HlsJsVideo src={src} autoPlay crossOrigin="anonymous" muted playsInline loop>
          <track kind="captions" src="/docs/demos/captions-button/captions.vtt" srcLang="en" label="English" />
          <track kind="subtitles" src="/docs/demos/captions-button/captions.vtt" srcLang="es" label="Spanish" />
        </HlsJsVideo>
        <div className="menu-bar">
          <SettingsMenu />
        </div>
      </Container>
    </Player>
  );
}
```

**App.css**

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

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

.menu-bar {
  position: absolute;
  right: 10px;
  bottom: 10px;
}

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

.menu {
  --media-menu-side-offset: 8px;
  --menu-transition-duration: 220ms;
  position: relative;
  box-sizing: border-box;
  width: var(--media-menu-width);
  min-width: 180px;
  max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
  height: var(--media-menu-height);
  max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
  padding: 6px;
  margin: 0;
  overflow: hidden;
  overscroll-behavior: none;
  font-size: 14px;
  color: white;
  background: rgba(0, 0, 0, 0.88);
  border: 0;
  border-radius: 8px;
  backdrop-filter: blur(10px);
  transition-timing-function: ease-in-out;
  transition-duration: var(--menu-transition-duration);
  transition-property: width, height, opacity, filter;
}

.menu-content,
.menu-panel {
  position: absolute;
  inset: 0;
  display: grid;
  gap: 2px;
  padding: 6px;
  overflow: auto;
  overscroll-behavior: none;
  outline: none;
  translate: 0 0;
  transition-timing-function: ease-in-out;
  transition-duration: var(--menu-transition-duration);
  transition-property: translate, filter;
}

.menu-content[data-child-open] {
  filter: blur(8px);
  translate: -100% 0;
}

.menu-panel[data-submenu] {
  z-index: 1;
}

.menu-panel[data-submenu][data-starting-style],
.menu-panel[data-submenu][data-ending-style] {
  overflow: hidden;
  pointer-events: none;
  filter: blur(8px);
  translate: 100% 0;
}

.menu-item,
.menu-back {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 32px;
  padding: 0 10px;
  font: inherit;
  color: inherit;
  cursor: pointer;
  background: none;
  border: 0;
  border-radius: 6px;
}

.menu-item[data-highlighted],
.menu-back:hover {
  background: rgba(255, 255, 255, 0.16);
}

.menu-back {
  gap: 8px;
  justify-content: flex-start;
}

.menu-value {
  display: inline-flex;
  gap: 8px;
  align-items: center;
  color: rgba(255, 255, 255, 0.72);
}

.menu-group {
  display: grid;
  gap: 2px;
}

.menu-tier {
  margin-left: 2px;
  font-size: 10px;
}

.menu-badge {
  margin-left: auto;
  color: rgba(255, 255, 255, 0.72);
}

.menu-back [aria-hidden],
[role="menuitemradio"] [aria-hidden],
.menu-item [aria-hidden] {
  font-size: 18px;
  line-height: 1;
  color: rgba(255, 255, 255, 0.72);
}

.menu-indicator {
  opacity: 0;
}

[role="menuitemradio"][aria-checked="true"] .menu-indicator {
  opacity: 1;
}
```

## API Reference

### Root

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `'start' \| 'center' \| 'end'` | `'start'` | Alignment along the trigger's edge. Root menus only. |
| `boundary` | `'viewport' \| 'container' \| string & {}` | — | Boundary used to constrain the root menu popup. |
| `closeOnEscape` | `boolean` | `true` | Close the menu when Escape is pressed at root level. |
| `closeOnOutsideClick` | `boolean` | `true` | Close the menu when clicking outside. Root menus only. |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled). |
| `open` | `boolean` | `false` | Controlled open state. |
| `side` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Preferred side of the trigger for the menu. Root menus only. |

#### 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' \| undefined` | Preferred side of the trigger for the menu. Root menus only. |
| `align` | `'start' \| 'center' \| 'end' \| undefined` | |
| `isSubmenu` | `boolean` | Whether this menu is nested inside another menu's content. |

#### CSS custom properties

| Variable | Description |
| --- | --- |
| `--media-menu-width` | Width of the active menu panel (px). |
| `--media-menu-height` | Height of the active menu panel (px). |
| `--media-menu-available-width` | Viewport-constrained max width for the menu (px). |
| `--media-menu-available-height` | Viewport-constrained max height for the menu (px). |

### CheckboxItem

A checkbox-style menu item. Renders a `<div>` with `role="menuitemcheckbox"`.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` | — | Whether the item is currently checked. |
| `className` | `string \| ((state: MenuState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `disabled` | `boolean` | — | Whether the item is disabled. |
| `onCheckedChange` | `((checked: boolean) => void)` | — | Called when the checked state should change. |
| `render` | `ReactElement \| ((props: HTMLProps, state: MenuState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: MenuState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

#### Data attributes

| Attribute | Description |
| --- | --- |
| `data-item` | Present on all navigable item types: Item, RadioItem, CheckboxItem, and the Trigger when acting as a submenu trigger inside a parent menu. Use `[data-item]` as a shared selector to target all item types at once. |
| `data-highlighted` | Present when the item is highlighted. Set to `pointer` when pointer movement caused the highlight; otherwise empty. |

### Content

Accessible item and focus scope for exactly one root or nested menu page.

#### Props

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

### Group

Groups related menu items. Renders a `<div>` with `role="group"`.

#### Props

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

### GroupLabel

Non-interactive label for a group of items. Renders a `<div>`.

#### Props

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

### Item

A single action in the menu. Renders a `<div>` with `role="menuitem"`.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: MenuState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `disabled` | `boolean` | — | Whether the item is disabled. |
| `onSelect` | `(() => void)` | — | Called when the item is selected. |
| `render` | `ReactElement \| ((props: HTMLProps, state: MenuState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: MenuState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

#### Data attributes

| Attribute | Description |
| --- | --- |
| `data-item` | Present on all navigable item types: Item, RadioItem, CheckboxItem, and the Trigger when acting as a submenu trigger inside a parent menu. Use `[data-item]` as a shared selector to target all item types at once. |
| `data-highlighted` | Present when the item is highlighted. Set to `pointer` when pointer movement caused the highlight; otherwise empty. |

### ItemIndicator

Visual indicator for a checked state. Only renders when `checked` is `true` (or `forceMount` is set).

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` | — | Whether the indicator is currently shown. Typically bound to the parent item's checked state. |
| `className` | `string \| ((state: MenuState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `forceMount` | `boolean` | — | When `true`, renders even when unchecked (useful for animating out). Defaults to `false`. |
| `render` | `ReactElement \| ((props: HTMLProps, state: MenuState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: MenuState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

### Popup

Positioned menu surface that contains one or more sibling Content pages.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: MenuState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `keepMounted` | `boolean` | — | Keep the popup mounted, hidden, and inert while closed. |
| `render` | `ReactElement \| ((props: HTMLProps, state: MenuState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: MenuState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

### RadioGroup

A group of mutually exclusive radio items. Renders a `<div>` with `role="group"`.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: MenuState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `onValueChange` | `((value: string) => void)` | — | Called when the user selects a radio item. |
| `render` | `ReactElement \| ((props: HTMLProps, state: MenuState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: MenuState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |
| `value` | `string` | — | The currently selected value. |

### RadioItem

A radio-style menu item. Renders a `<div>` with `role="menuitemradio"`.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: MenuState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `disabled` | `boolean` | — | Whether the item is disabled. |
| `render` | `ReactElement \| ((props: HTMLProps, state: MenuState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: MenuState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |
| `value` | `string` | — | The value this item represents. |

#### Data attributes

| Attribute | Description |
| --- | --- |
| `data-item` | Present on all navigable item types: Item, RadioItem, CheckboxItem, and the Trigger when acting as a submenu trigger inside a parent menu. Use `[data-item]` as a shared selector to target all item types at once. |
| `data-highlighted` | Present when the item is highlighted. Set to `pointer` when pointer movement caused the highlight; otherwise empty. |

### Separator

Visual divider between groups of items. Renders a `<div>` with `role="separator"`.

#### Props

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

### Trigger

Button that toggles the menu visibility. At root level renders a `<button>`. When inside a parent menu (as a submenu trigger), renders as a `<div role="menuitem">` that opens the submenu on click or ArrowRight.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: MenuState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `disabled` | `boolean` | — | Disables the trigger. |
| `onClick` | `MouseEventHandler<HTMLButtonElement \| HTMLDivElement>` | — | |
| `onKeyDown` | `KeyboardEventHandler<HTMLButtonElement \| HTMLDivElement>` | — | |
| `render` | `ReactElement \| ((props: HTMLProps, state: MenuState) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: MenuState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

#### Data attributes

| Attribute | Description |
| --- | --- |
| `data-item` | Present on all navigable item types: Item, RadioItem, CheckboxItem, and the Trigger when acting as a submenu trigger inside a parent menu. Use `[data-item]` as a shared selector to target all item types at once. |
| `data-highlighted` | Present when the item is highlighted. Set to `pointer` when pointer movement caused the highlight; otherwise empty. |

---

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