VolumeSlider
A slider component for controlling media playback volume
Anatomy
<VolumeSlider.Root /><media-volume-slider></media-volume-slider>Behavior
Controls the media volume level. The slider maps its 0–100 internal range to the media’s 0–1 volume scale. When the media is muted, the fill level drops to 0 regardless of the stored volume value.
Styling
Use CSS custom properties to style the fill and pointer levels:
media-volume-slider::before {
width: calc(var(--media-slider-fill) * 1%);
}Accessibility
Renders with role="slider" and automatic aria-label of “Volume”. Override with the label prop. Keyboard controls:
- Arrow Left / Arrow Right: step by
stepincrement - Page Up / Page Down: step by
largeStepincrement - Home: set volume to 0
- End: set volume to max
Examples
Nest sub-components for full control over the slider’s DOM structure. This example includes a track, fill bar, draggable thumb, and a tooltip that shows the volume percentage on hover.
import { createPlayer, MuteButton, VolumeSlider } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './WithParts.css';
const Player = createPlayer({ features: videoFeatures });
export default function WithParts() {
return (
<Player.Provider>
<Player.Container className="react-volume-slider-parts">
<Video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<MuteButton
className="react-volume-slider-parts__mute-button"
render={(props, state) => <button {...props}>{state.muted ? 'Unmute' : 'Mute'}</button>}
/>
<VolumeSlider.Root className="react-volume-slider-parts__slider">
<VolumeSlider.Track className="react-volume-slider-parts__track">
<VolumeSlider.Fill className="react-volume-slider-parts__fill" />
</VolumeSlider.Track>
<VolumeSlider.Thumb className="react-volume-slider-parts__thumb" />
<VolumeSlider.Value type="pointer" className="react-volume-slider-parts__value" />
</VolumeSlider.Root>
</Player.Container>
</Player.Provider>
);
}
.react-volume-slider-parts {
position: relative;
}
.react-volume-slider-parts video {
width: 100%;
}
.react-volume-slider-parts__mute-button {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
cursor: pointer;
}
.react-volume-slider-parts__slider {
position: absolute;
bottom: 10px;
right: 10px;
width: 100px;
display: flex;
align-items: center;
height: 20px;
cursor: pointer;
}
.react-volume-slider-parts__track {
position: absolute;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
border-radius: 9999px;
transition: height 150ms ease;
}
.react-volume-slider-parts__slider[data-interactive] .react-volume-slider-parts__track {
height: 6px;
}
.react-volume-slider-parts__fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-fill);
background: white;
border-radius: 9999px;
}
.react-volume-slider-parts__thumb {
position: absolute;
left: var(--media-slider-fill);
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
transform: translateX(-50%) scale(0);
transition: transform 150ms ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.react-volume-slider-parts__slider[data-interactive] .react-volume-slider-parts__thumb {
transform: translateX(-50%) scale(1);
}
.react-volume-slider-parts__slider[data-dragging] .react-volume-slider-parts__thumb {
transform: translateX(-50%) scale(1.1);
}
.react-volume-slider-parts__value {
position: absolute;
left: var(--media-slider-pointer);
bottom: 100%;
transform: translateX(-50%);
margin-bottom: 6px;
background: rgba(0, 0, 0, 0.8);
color: white;
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
pointer-events: none;
white-space: nowrap;
opacity: 0;
transition: opacity 150ms ease;
}
.react-volume-slider-parts__slider[data-pointing] .react-volume-slider-parts__value {
opacity: 1;
}
<video-player class="html-volume-slider-parts">
<media-container>
<video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<media-mute-button class="html-volume-slider-parts__mute-button">
<span class="show-when-muted">Unmute</span>
<span class="show-when-unmuted">Mute</span>
</media-mute-button>
<media-volume-slider class="html-volume-slider-parts__slider">
<media-slider-track class="html-volume-slider-parts__track">
<media-slider-fill class="html-volume-slider-parts__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="html-volume-slider-parts__thumb"></media-slider-thumb>
<media-slider-value type="pointer" class="html-volume-slider-parts__value"></media-slider-value>
</media-volume-slider>
</media-container>
</video-player>
.html-volume-slider-parts,
.html-volume-slider-parts media-container {
display: block;
position: relative;
}
.html-volume-slider-parts video {
width: 100%;
}
.html-volume-slider-parts__mute-button {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
cursor: pointer;
}
.html-volume-slider-parts__mute-button .show-when-muted {
display: none;
}
.html-volume-slider-parts__mute-button .show-when-unmuted {
display: none;
}
.html-volume-slider-parts__mute-button[data-muted] .show-when-muted {
display: inline;
}
.html-volume-slider-parts__mute-button:not([data-muted]) .show-when-unmuted {
display: inline;
}
.html-volume-slider-parts__slider {
position: absolute;
bottom: 10px;
right: 10px;
width: 100px;
display: flex;
align-items: center;
height: 20px;
cursor: pointer;
}
.html-volume-slider-parts__track {
position: absolute;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
border-radius: 9999px;
transition: height 150ms ease;
}
.html-volume-slider-parts__slider[data-interactive] .html-volume-slider-parts__track {
height: 6px;
}
.html-volume-slider-parts__fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-fill);
background: white;
border-radius: 9999px;
}
.html-volume-slider-parts__thumb {
position: absolute;
left: var(--media-slider-fill);
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
transform: translateX(-50%) scale(0);
transition: transform 150ms ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.html-volume-slider-parts__slider[data-interactive] .html-volume-slider-parts__thumb {
transform: translateX(-50%) scale(1);
}
.html-volume-slider-parts__slider[data-dragging] .html-volume-slider-parts__thumb {
transform: translateX(-50%) scale(1.1);
}
.html-volume-slider-parts__value {
position: absolute;
left: var(--media-slider-pointer);
bottom: 100%;
transform: translateX(-50%);
margin-bottom: 6px;
background: rgba(0, 0, 0, 0.8);
color: white;
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
pointer-events: none;
white-space: nowrap;
opacity: 0;
transition: opacity 150ms ease;
}
.html-volume-slider-parts__slider[data-pointing] .html-volume-slider-parts__value {
opacity: 1;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/mute-button';
import '@videojs/html/ui/volume-slider';
API Reference
Root media-volume-slider
Props
| Prop | Type | Default | |
|---|---|---|---|
disabled | boolean | — | |
| |||
label | string | function | 'Volume' | |
| |||
largeStep | number | — | |
| |||
max | number | — | |
min | number | — | |
orientation | 'horizontal' | 'vertical' | — | |
| |||
step | number | — | |
| |||
thumbAlignment | 'center' | 'edge' | — | |
| |||
value | number | — | |
State
render, className, and style props.
| Property | Type | |
|---|---|---|
value | number | |
| ||
fillPercent | number | |
| ||
pointerPercent | number | |
| ||
dragging | boolean | |
| ||
pointing | boolean | |
| ||
interactive | boolean | |
| ||
orientation | 'horizontal' | 'vertical' | |
| ||
disabled | boolean | |
| ||
thumbAlignment | 'center' | 'edge' | |
| ||
muted | boolean | |
| ||
volume | number | |
| ||
CSS custom properties
| Variable | |
|---|---|
--media-slider-fill | |
| |
--media-slider-pointer | |
| |
Fill media-slider-fill
Displays the filled portion from start to the current value.
Preview media-slider-preview
Positioning container for preview content that tracks the pointer along the slider.
Props
| Prop | Type | Default | |
|---|---|---|---|
overflow | SliderPreviewOverflow | — | |
| |||
Data attributes
| Attribute | Type | |
|---|---|---|
data-dragging | ||
| ||
data-pointing | ||
| ||
data-interactive | ||
| ||
data-orientation | 'horizontal' | 'vertical' | |
| ||
data-disabled | ||
| ||
Thumb media-slider-thumb
Draggable handle for setting the slider value. Receives focus and handles keyboard interaction.
Data attributes
| Attribute | Type | |
|---|---|---|
data-dragging | ||
| ||
data-pointing | ||
| ||
data-interactive | ||
| ||
data-orientation | 'horizontal' | 'vertical' | |
| ||
data-disabled | ||
| ||
Track media-slider-track
Contains the slider's visual track and interactive hit zone.
Value media-slider-value
Displays a formatted text representation of the slider value. Renders an <output> element.
Props
| Prop | Type | Default | |
|---|---|---|---|
format | ((value: number) => string) | — | |
| |||
type | "current" | "pointer" | — | |
| |||
Data attributes
| Attribute | Type | |
|---|---|---|
data-dragging | ||
| ||
data-pointing | ||
| ||
data-interactive | ||
| ||
data-orientation | 'horizontal' | 'vertical' | |
| ||
data-disabled | ||
| ||