Skip to content
FrameworkStyle

Menu

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

Anatomy

<Menu.Root>
  <Menu.Trigger>Settings</Menu.Trigger>
  <Menu.Content>
    <Menu.View>
      <Menu.Root>
        <Menu.Trigger type="playback-rate">
          Speed <Menu.ItemValue />
        </Menu.Trigger>
        <Menu.Content>
          <Menu.Back />
          <Menu.RadioGroup value={rate} onValueChange={setRate}>
            <Menu.GroupLabel>Speed</Menu.GroupLabel>
            <Menu.RadioItem value="1">Normal</Menu.RadioItem>
            <Menu.RadioItem value="1.5">1.5x</Menu.RadioItem>
          </Menu.RadioGroup>
        </Menu.Content>
      </Menu.Root>

      <Menu.Root>
        <Menu.Trigger type="captions">
          Captions <Menu.ItemValue />
        </Menu.Trigger>
        <Menu.Content>
          <Menu.Back />
          <Menu.RadioGroup value={captions} onValueChange={setCaptions}>
            <Menu.GroupLabel>Captions</Menu.GroupLabel>
            <Menu.RadioItem value="off">Off</Menu.RadioItem>
            <Menu.RadioItem value="en">English</Menu.RadioItem>
          </Menu.RadioGroup>
        </Menu.Content>
      </Menu.Root>

      <Menu.Item onSelect={copyLink}>Copy link</Menu.Item>
    </Menu.View>
  </Menu.Content>
</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 are positioned against their trigger with side and align.

Nest Menu.Root inside Menu.Content to create an in-place submenu. In HTML, link a submenu with commandfor. Wrap the root list in Menu.View or <media-menu-view> when the root list and child menus share one animated viewport.

Set type="playback-rate" or type="captions" on a submenu trigger when it represents a media setting. Menu.ItemValue and <media-menu-item-value> read that setting context and display the current value, such as 1.5x, English, or Off.

Styling

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

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

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

[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 or <media-menu-group-label> inside grouped choices so the group receives an accessible label.

Examples

Basic usage

import { createPlayer, Menu, useCaptionsOptions, usePlaybackRateOptions } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import type { ReactNode } from 'react';

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

function SettingsMenu(): ReactNode {
  const playbackRate = usePlaybackRateOptions();
  const captions = useCaptionsOptions();
  const hasPlaybackRate = playbackRate?.state.availability === 'available';
  const hasCaptions = captions?.state.availability === 'available';

  if (!hasPlaybackRate && !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.Content className="menu">
        <Menu.View className="menu-panel">
          {hasPlaybackRate && playbackRate ? (
            <Menu.Root>
              <Menu.Trigger
                type="playback-rate"
                className="menu-item"
                render={(props) => (
                  <div {...props}>
                    <span>Speed</span>
                    <span className="menu-value">
                      <Menu.ItemValue />
                      <span aria-hidden="true"></span>
                    </span>
                  </div>
                )}
              />
              <Menu.Content className="menu-panel">
                <Menu.Back className="menu-back">Speed</Menu.Back>
                <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>

                      </Menu.ItemIndicator>
                    </Menu.RadioItem>
                  ))}
                </Menu.RadioGroup>
              </Menu.Content>
            </Menu.Root>
          ) : null}

          {hasCaptions && captions ? (
            <Menu.Root>
              <Menu.Trigger
                type="captions"
                className="menu-item"
                render={(props) => (
                  <div {...props}>
                    <span>Captions</span>
                    <span className="menu-value">
                      <Menu.ItemValue />
                      <span aria-hidden="true"></span>
                    </span>
                  </div>
                )}
              />
              <Menu.Content className="menu-panel">
                <Menu.Back className="menu-back">Captions</Menu.Back>
                <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>

                      </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.View>
      </Menu.Content>
    </Menu.Root>
  );
}

export default function BasicUsage() {
  return (
    <Player.Provider>
      <Player.Container className="media-container">
        <Video
          src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
          autoPlay
          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" />
        </Video>
        <div className="menu-bar">
          <SettingsMenu />
        </div>
      </Player.Container>
    </Player.Provider>
  );
}

API Reference

Root

Props

Prop Type Default Details

State

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

Property Type Details

Data attributes

Attribute Type Details

CSS custom properties

Variable Details

Back

Button that navigates back to the parent menu view. Place at the top of a submenu Content.

Props

Prop Type Default Details

CheckboxItem

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

Props

Prop Type Default Details

Content

Container for menu items. Positioned relative to the trigger at root level; renders in-place as a submenu panel when nested.

Data attributes

Attribute Type Details

Group

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

GroupLabel

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

Item

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

Props

Prop Type Default Details

ItemIndicator

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

Props

Prop Type Default Details

ItemValue

Displays the current value for a settings menu item from Menu.Item or Menu.Trigger context.

Data attributes

Attribute Type Details

RadioGroup

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

Props

Prop Type Default Details

RadioItem

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

Props

Prop Type Default Details

Separator

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

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 pushes the submenu on click or ArrowRight.

Props

Prop Type Default Details

View

Root menu view inside the menu viewport.