AudioTrackRadioGroup
A menu radio group for selecting an audio track
Creates radio items from the player audio track state and selects the enabled track.
Import
import { AudioTrackRadioGroup } from '@videojs/react';import '@videojs/html/ui/audio-track-radio-group';Anatomy
<AudioTrackRadioGroup.Root>
<AudioTrackRadioGroup.Value />
<AudioTrackRadioGroup.Options
renderItem={(props, item) => (
<Menu.RadioItem {...props}>
{item.label}
<Menu.ItemIndicator checked={item.checked} />
</Menu.RadioItem>
)}
/>
</AudioTrackRadioGroup.Root><media-audio-track-radio-group>
<template>
<media-menu-radio-item>
<span data-part="label"></span>
<media-menu-item-indicator></media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-audio-track-radio-group>Behavior
The group is available when the configured media exposes more than one audio track. Labels use the track label, then language, then kind. Pass formatTrack to customize the visible labels.
AudioTrackRadioGroup.Root owns selection and publishes option state to an enclosing Menu. Value displays the selected label. Options generates each item through its required renderItem callback and returns null when the audio track feature is not configured.
<media-audio-track-radio-group> generates <media-menu-radio-item> children. Add an optional <template> with a single <media-menu-radio-item> to customize each generated item.
Styling
| Attribute | Values | Description |
|---|---|---|
data-audio-track |
string |
Current audio track value. |
data-disabled |
Present / absent | Present when audio track selection is disabled. |
data-hidden |
Present / absent | Present when multiple audio tracks are unavailable. |
data-availability |
"available" / "unavailable" |
Whether multiple audio tracks are available. |
Unavailable groups receive the native hidden attribute.
Accessibility
The group uses the menu radio group pattern.
The component receives an accessible label from the label prop or defaults to Audio. Override it with aria-label or aria-labelledby.
The element receives an accessible label from the label property or defaults to Audio.
Examples
Basic usage
import { AudioTrackRadioGroup, Container, createPlayer, Menu } 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 AudioMenu(): ReactNode {
return (
<Menu.Root side="top" align="end">
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
Audio
</Menu.Trigger>
<Menu.Popup className="menu">
<Menu.Content>
<AudioTrackRadioGroup.Root>
<AudioTrackRadioGroup.Options
className="menu-group"
renderItem={(props, item) => (
<Menu.RadioItem {...props} className="menu-item">
{item.label}
<Menu.ItemIndicator checked={item.checked} forceMount className="menu-indicator">
✓
</Menu.ItemIndicator>
</Menu.RadioItem>
)}
/>
</AudioTrackRadioGroup.Root>
</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 />
<div className="menu-bar">
<AudioMenu />
</div>
</Container>
</Player>
);
}
.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;
box-sizing: border-box;
display: grid;
gap: 2px;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
margin: 0;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border: 0;
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
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] {
background: rgba(255, 255, 255, 0.16);
}
.menu-indicator {
opacity: 0;
}
[role="menuitemradio"][aria-checked="true"] .menu-indicator {
opacity: 1;
}
<video-player class="video-player">
<media-container>
<hlsjs-video
class="video-media"
src="https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8"
autoplay
crossorigin="anonymous"
muted
playsinline
loop
></hlsjs-video>
<div class="menu-bar">
<button type="button" commandfor="audio-menu" class="settings-trigger">Audio</button>
<media-menu id="audio-menu" side="top" align="end" class="menu">
<media-menu-content>
<media-audio-track-radio-group class="menu-group" label="Audio tracks">
<template>
<media-menu-radio-item class="menu-item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="menu-indicator">✓</media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-audio-track-radio-group>
</media-menu-content>
</media-menu>
</div>
</media-container>
</video-player>
.video-player media-container {
position: relative;
display: block;
}
.video-media {
width: 100%;
}
.menu-bar {
position: absolute;
right: 10px;
bottom: 10px;
}
.menu-bar:has([data-availability="unavailable"]) {
display: none;
}
.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;
box-sizing: border-box;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
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] {
background: rgba(255, 255, 255, 0.16);
}
.menu-indicator {
opacity: 0;
}
media-menu-radio-item[aria-checked="true"] .menu-indicator {
opacity: 1;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/media/hlsjs-video';
import '@videojs/html/ui/menu';
import '@videojs/html/ui/audio-track-radio-group';
API Reference
Valuemedia-audio-track-radio-group
Displays the selected audio-track label.
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: AudioTrackOptionsResult) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: AudioTrackOptionsResult) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: AudioTrackOptionsResult) => CSSProperties | undefined) | — | |
| |||
Events
| Event | Description |
|---|---|
value-change | Fired when the selected value changes. |