PlaybackRateRadioGroup
A menu radio group for selecting playback speed
Creates radio items from the player playback rate state and selects the playback speed.
Anatomy
<Menu.RadioGroup>
<Menu.GroupLabel />
<Menu.RadioItem>
<Menu.ItemIndicator />
</Menu.RadioItem>
</Menu.RadioGroup>
<media-playback-rate-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-playback-rate-radio-group>
Behavior
The group is available when the configured media exposes playback rates. Option labels default to the rate followed by a multiplication sign (e.g. 1.5×); pass formatRate to customize them.
usePlaybackRateOptions returns null when the playback rate feature is not configured. Use the returned options with Menu.RadioGroup.
<media-playback-rate-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-rate
|
number
|
Current playback rate. |
data-disabled
|
Present / absent | Present when playback rate selection is disabled. |
data-availability
|
"available" / "unavailable"
|
Whether playback rates are available. |
media-playback-rate-radio-group[data-availability="unavailable"] {
display: none;
}
Accessibility
The group uses the menu radio group pattern.
Give Menu.RadioGroup an accessible name with aria-label or a nested Menu.GroupLabel.
The element defaults to Playback rate {rate} (e.g. Playback rate 1.5). Override its accessible name with aria-label or aria-labelledby.
Examples
Basic usage
import { createPlayer, Menu, usePlaybackRateOptions } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import type { ReactNode } from 'react';
const Player = createPlayer({ features: videoFeatures });
function SpeedMenu(): ReactNode {
const playbackRate = usePlaybackRateOptions();
if (playbackRate?.state.availability !== 'available') return null;
return (
<Menu.Root side="top" align="end">
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
Speed
</Menu.Trigger>
<Menu.Content className="menu">
<Menu.RadioGroup
className="menu-group"
value={playbackRate.value}
onValueChange={playbackRate.setValue}
aria-label="Speed"
>
{playbackRate.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled} className="menu-item">
{option.label}
<Menu.ItemIndicator checked={option.value === playbackRate.value} forceMount className="menu-indicator">
✓
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</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 />
<div className="menu-bar">
<SpeedMenu />
</div>
</Player.Container>
</Player.Provider>
);
}
.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>
<video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<div class="menu-bar">
<button type="button" commandfor="speed-menu" class="settings-trigger">Speed</button>
<media-menu id="speed-menu" side="top" align="end" class="menu">
<media-playback-rate-radio-group class="menu-group" aria-label="Speed">
<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-playback-rate-radio-group>
</media-menu>
</div>
</media-container>
</video-player>
.video-player,
.video-player media-container {
position: relative;
display: block;
}
.video-player 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;
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/menu';
import '@videojs/html/ui/playback-rate-radio-group';
API Reference
Props
| Prop | Type | Default | Details |
|---|---|---|---|
disabled
|
boolean
|
false
|
|
|
|||
formatRate
|
function
|
formatPlaybackRate
|
|
|
|||
label
|
string | function
|
''
|
|
|
|||
State
render, className, and style props.
| Property | Type | Details |
|---|---|---|
rate
|
number
|
|
rates
|
number[]
|
|
disabled
|
boolean
|
|
availability
|
'available' | 'unavailable'
|
|
label
|
string
|
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-rate
|
number
|
|
|
||
data-disabled
|
||
|
||
data-availability
|
'available' | 'unavailable'
|
|
|
||