Slider
A composable slider component with track, fill, thumb, preview, and value parts
Anatomy
<Slider.Root>
<Slider.Track>
<Slider.Fill />
</Slider.Track>
<Slider.Thumb />
<Slider.Preview>
<Slider.Value type="pointer" />
</Slider.Preview>
</Slider.Root>
<media-slider>
<media-slider-track>
<media-slider-fill></media-slider-fill>
</media-slider-track>
<media-slider-thumb></media-slider-thumb>
<media-slider-preview>
<media-slider-value type="pointer"></media-slider-value>
</media-slider-preview>
</media-slider>
Behavior
The base Slider provides a generic range input. It manages value, pointer tracking, and drag interactions. Domain-specific sliders like TimeSlider and VolumeSlider extend this with media-specific bindings.
The slider supports vertical orientation via the orientation prop (defaults to "horizontal").
Styling
Use CSS custom properties to position fill, thumb, and preview elements:
media-slider-fill {
width: var(--media-slider-fill);
}
media-slider-thumb {
left: var(--media-slider-fill);
}
React renders standard DOM elements. Add a className to style them:
.slider-fill {
width: var(--media-slider-fill);
}
.slider-thumb {
left: var(--media-slider-fill);
}
Style based on interaction state:
media-slider[data-interactive] media-slider-track {
height: 6px;
}
media-slider[data-pointing] media-slider-preview {
opacity: 1;
}
.slider[data-interactive] .slider-track {
height: 6px;
}
.slider[data-pointing] .slider-preview {
opacity: 1;
}
Accessibility
Renders with role="slider" and automatic ARIA attributes (aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext). Override the label with the label prop. Keyboard controls:
-
Arrow Left / Arrow Right: step by
stepincrement -
Page Up / Page Down: step by
largeStepincrement - Home: jump to minimum
- End: jump to maximum
Examples
Basic
A slider with track, fill, and thumb.
import { Slider } from '@videojs/react';
import { useState } from 'react';
export default function BasicUsage() {
const [value, setValue] = useState(50);
return (
<div className="demo">
<Slider.Root className="media-slider" value={value} onValueChange={setValue}>
<Slider.Track className="media-slider-track">
<Slider.Fill className="media-slider-fill" />
</Slider.Track>
<Slider.Thumb className="media-slider-thumb" />
</Slider.Root>
</div>
);
}
.demo {
display: flex;
align-items: center;
padding: 24px;
background: #1a1a1a;
}
.media-slider {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: 20px;
cursor: pointer;
}
.media-slider-track {
position: absolute;
right: 0;
left: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.media-slider[data-interactive] .media-slider-track {
height: 6px;
}
.media-slider-fill {
position: absolute;
top: 0;
left: 0;
width: var(--media-slider-fill);
height: 100%;
background: white;
border-radius: 9999px;
}
.media-slider-thumb {
position: absolute;
left: var(--media-slider-fill);
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
transform: translateX(-50%) scale(0);
transition: transform 150ms ease;
}
.media-slider[data-interactive] .media-slider-thumb {
transform: translateX(-50%) scale(1);
}
.media-slider[data-dragging] .media-slider-thumb {
transform: translateX(-50%) scale(1.1);
}
<div class="demo">
<media-slider class="media-slider" value="50">
<media-slider-track class="media-slider-track">
<media-slider-fill class="media-slider-fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider-thumb"></media-slider-thumb>
</media-slider>
</div>
.demo {
display: flex;
align-items: center;
padding: 24px;
background: #1a1a1a;
}
.media-slider {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: 20px;
cursor: pointer;
}
.media-slider-track {
position: absolute;
right: 0;
left: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.media-slider[data-interactive] .media-slider-track {
height: 6px;
}
.media-slider-fill {
position: absolute;
top: 0;
left: 0;
width: var(--media-slider-fill);
height: 100%;
background: white;
border-radius: 9999px;
}
.media-slider-thumb {
position: absolute;
left: var(--media-slider-fill);
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
transform: translateX(-50%) scale(0);
transition: transform 150ms ease;
}
.media-slider[data-interactive] .media-slider-thumb {
transform: translateX(-50%) scale(1);
}
.media-slider[data-dragging] .media-slider-thumb {
transform: translateX(-50%) scale(1.1);
}
import '@videojs/html/ui/slider';
With Preview
A slider with a pointer-tracking preview that displays the value at the current pointer position.
import { Slider } from '@videojs/react';
import { useState } from 'react';
export default function WithPreview() {
const [value, setValue] = useState(50);
return (
<div className="demo">
<Slider.Root className="media-slider" value={value} onValueChange={setValue}>
<Slider.Track className="media-slider-track">
<Slider.Fill className="media-slider-fill" />
</Slider.Track>
<Slider.Thumb className="media-slider-thumb" />
<Slider.Preview className="preview">
<Slider.Value type="pointer" className="media-slider-value" />
</Slider.Preview>
</Slider.Root>
</div>
);
}
.demo {
display: flex;
align-items: center;
padding: 40px 24px;
background: #1a1a1a;
}
.media-slider {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: 20px;
cursor: pointer;
}
.media-slider-track {
position: absolute;
right: 0;
left: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.media-slider[data-interactive] .media-slider-track {
height: 6px;
}
.media-slider-fill {
position: absolute;
top: 0;
left: 0;
width: var(--media-slider-fill);
height: 100%;
background: white;
border-radius: 9999px;
}
.media-slider-thumb {
position: absolute;
left: var(--media-slider-fill);
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
transform: translateX(-50%) scale(0);
transition: transform 150ms ease;
}
.media-slider[data-interactive] .media-slider-thumb {
transform: translateX(-50%) scale(1);
}
.media-slider[data-dragging] .media-slider-thumb {
transform: translateX(-50%) scale(1.1);
}
.preview {
position: absolute;
bottom: 100%;
margin-bottom: 6px;
pointer-events: none;
opacity: 0;
transition: opacity 150ms ease;
}
.media-slider[data-pointing] .preview {
opacity: 1;
}
.media-slider-value {
padding: 2px 6px;
font-size: 12px;
color: white;
white-space: nowrap;
background: rgba(0, 0, 0, 0.8);
border-radius: 4px;
}
<div class="demo">
<media-slider class="media-slider" value="50">
<media-slider-track class="media-slider-track">
<media-slider-fill class="media-slider-fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="media-slider-thumb"></media-slider-thumb>
<media-slider-preview class="preview">
<media-slider-value type="pointer" class="media-slider-value"></media-slider-value>
</media-slider-preview>
</media-slider>
</div>
.demo {
display: flex;
align-items: center;
padding: 40px 24px;
background: #1a1a1a;
}
.media-slider {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: 20px;
cursor: pointer;
}
.media-slider-track {
position: absolute;
right: 0;
left: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.media-slider[data-interactive] .media-slider-track {
height: 6px;
}
.media-slider-fill {
position: absolute;
top: 0;
left: 0;
width: var(--media-slider-fill);
height: 100%;
background: white;
border-radius: 9999px;
}
.media-slider-thumb {
position: absolute;
left: var(--media-slider-fill);
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
transform: translateX(-50%) scale(0);
transition: transform 150ms ease;
}
.media-slider[data-interactive] .media-slider-thumb {
transform: translateX(-50%) scale(1);
}
.media-slider[data-dragging] .media-slider-thumb {
transform: translateX(-50%) scale(1.1);
}
.preview {
position: absolute;
bottom: 100%;
margin-bottom: 6px;
pointer-events: none;
opacity: 0;
transition: opacity 150ms ease;
}
.media-slider[data-pointing] .preview {
opacity: 1;
}
.media-slider-value {
padding: 2px 6px;
font-size: 12px;
color: white;
white-space: nowrap;
background: rgba(0, 0, 0, 0.8);
border-radius: 4px;
}
import '@videojs/html/ui/slider';
API Reference
Root
media-slider
Props
| Prop | Type | Default | Details |
|---|---|---|---|
disabled
|
boolean
|
false
|
|
|
|||
label
|
string | function
|
''
|
|
|
|||
largeStep
|
number
|
10
|
|
|
|||
max
|
number
|
100
|
|
|
|||
min
|
number
|
0
|
|
|
|||
orientation
|
'horizontal' | 'vertical'
|
'horizontal'
|
|
|
|||
step
|
number
|
1
|
|
|
|||
thumbAlignment
|
'center' | 'edge'
|
'center'
|
|
|
|||
value
|
number
|
0
|
|
|
|||
State
render, className, and style props.
| Property | Type | Details |
|---|---|---|
value
|
number
|
|
|
||
fillPercent
|
number
|
|
|
||
pointerPercent
|
number
|
|
|
||
dragging
|
boolean
|
|
|
||
pointing
|
boolean
|
|
|
||
interactive
|
boolean
|
|
|
||
orientation
|
'horizontal' | 'vertical'
|
|
|
||
disabled
|
boolean
|
|
|
||
thumbAlignment
|
'center' | 'edge'
|
|
|
||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-dragging
|
||
|
||
data-pointing
|
||
|
||
data-interactive
|
||
|
||
data-orientation
|
'horizontal' | 'vertical'
|
|
|
||
data-disabled
|
||
|
||
CSS custom properties
| Variable | Details |
|---|---|
--media-slider-fill
|
|
|
|
--media-slider-pointer
|
|
|
|
--media-slider-buffer
|
|
|
|
Buffer
media-slider-buffer
Displays the buffered range on the slider track.
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 | Details |
|---|---|---|---|
overflow
|
SliderPreviewOverflow
|
—
|
|
|
|||
Data attributes
| Attribute | Type | Details |
|---|---|---|
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 | Details |
|---|---|---|
data-dragging
|
||
|
||
data-pointing
|
||
|
||
data-interactive
|
||
|
||
data-orientation
|
'horizontal' | 'vertical'
|
|
|
||
data-disabled
|
||
|
||
Thumbnail
media-slider-thumbnail
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 | Details |
|---|---|---|---|
format
|
((value: number) => string)
|
—
|
|
|
|||
type
|
"current" | "pointer"
|
—
|
|
|
|||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-dragging
|
||
|
||
data-pointing
|
||
|
||
data-interactive
|
||
|
||
data-orientation
|
'horizontal' | 'vertical'
|
|
|
||
data-disabled
|
||
|
||