TimeSlider
A slider component for seeking through media playback time
Anatomy
<TimeSlider.Root />
<media-time-slider></media-time-slider>
Behavior
Displays and controls the current playback position. Dragging the slider seeks the media. The fill level reflects currentTime / duration as a percentage, and the buffer level shows how much media has been buffered.
Value changes during drag are throttled via the changeThrottle prop (default 100ms) using a leading+trailing throttle to keep the UI responsive without overwhelming the media element.
Styling
Use CSS custom properties to style the fill, pointer, and buffer levels:
media-time-slider::before {
width: var(--media-slider-fill);
}
React renders a <div> element. Add a className to style it:
.time-slider::before {
width: var(--media-slider-fill);
}
Use data-seeking to style during active seek operations:
media-time-slider[data-seeking] {
opacity: 0.8;
}
.time-slider[data-seeking] {
opacity: 0.8;
}
Accessibility
Renders with role="slider" and automatic aria-label of “Seek”. Override with the label prop. Keyboard controls:
-
Arrow Left / Arrow Right: step by
stepincrement -
Page Up / Page Down: step by
largeStepincrement - Home: seek to start
- End: seek to end
Examples
Nest sub-components for full control over the slider’s DOM structure. This example includes a track, fill bar, buffer indicator, draggable thumb, and a tooltip that shows the pointed-at time.
import { createPlayer, TimeSlider } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const Player = createPlayer({ features: videoFeatures });
export default function WithParts() {
return (
<Player.Provider>
<Player.Container className="media-container">
<Video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<TimeSlider.Root className="media-time-slider">
<TimeSlider.Track className="media-slider-track">
<TimeSlider.Buffer className="media-slider-buffer" />
<TimeSlider.Fill className="media-slider-fill" />
</TimeSlider.Track>
<TimeSlider.Thumb className="media-slider-thumb" />
<TimeSlider.Value type="pointer" className="media-slider-value" />
</TimeSlider.Root>
</Player.Container>
</Player.Provider>
);
}
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.media-time-slider {
position: absolute;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
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-time-slider[data-interactive] .media-slider-track {
height: 6px;
}
.media-slider-buffer {
position: absolute;
top: 0;
left: 0;
width: var(--media-slider-buffer);
height: 100%;
background: rgba(255, 255, 255, 0.4);
border-radius: 9999px;
}
.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-time-slider[data-interactive] .media-slider-thumb {
transform: translateX(-50%) scale(1);
}
.media-time-slider[data-dragging] .media-slider-thumb {
left: var(--media-slider-pointer);
transform: translateX(-50%) scale(1.1);
}
.media-time-slider[data-dragging] .media-slider-fill {
width: var(--media-slider-pointer);
}
.media-slider-value {
position: absolute;
bottom: 100%;
left: var(--media-slider-pointer);
padding: 2px 6px;
margin-bottom: 6px;
font-size: 12px;
color: white;
white-space: nowrap;
pointer-events: none;
background: rgba(0, 0, 0, 0.8);
border-radius: 4px;
opacity: 0;
transform: translateX(-50%);
transition: opacity 150ms ease;
}
.media-time-slider[data-pointing] .media-slider-value {
opacity: 1;
}
<video-player class="video-player">
<media-container>
<video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<media-time-slider class="media-time-slider">
<media-slider-track class="media-slider-track">
<media-slider-buffer class="media-slider-buffer"></media-slider-buffer>
<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-value type="pointer" class="media-slider-value"></media-slider-value>
</media-time-slider>
</media-container>
</video-player>
.video-player,
.video-player media-container {
position: relative;
display: block;
}
.video-player video {
width: 100%;
}
.media-time-slider {
position: absolute;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
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-time-slider[data-interactive] .media-slider-track {
height: 6px;
}
.media-slider-buffer {
position: absolute;
top: 0;
left: 0;
width: var(--media-slider-buffer);
height: 100%;
background: rgba(255, 255, 255, 0.4);
border-radius: 9999px;
}
.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-time-slider[data-interactive] .media-slider-thumb {
transform: translateX(-50%) scale(1);
}
.media-time-slider[data-dragging] .media-slider-thumb {
left: var(--media-slider-pointer);
transform: translateX(-50%) scale(1.1);
}
.media-time-slider[data-dragging] .media-slider-fill {
width: var(--media-slider-pointer);
}
.media-slider-value {
position: absolute;
bottom: 100%;
left: var(--media-slider-pointer);
padding: 2px 6px;
margin-bottom: 6px;
font-size: 12px;
color: white;
white-space: nowrap;
pointer-events: none;
background: rgba(0, 0, 0, 0.8);
border-radius: 4px;
opacity: 0;
transform: translateX(-50%);
transition: opacity 150ms ease;
}
.media-time-slider[data-pointing] .media-slider-value {
opacity: 1;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/time-slider';
API Reference
Root
media-time-slider
Props
| Prop | Type | Default | Details |
|---|---|---|---|
changeThrottle
|
number
|
100
|
|
|
|||
disabled
|
boolean
|
—
|
|
|
|||
label
|
string | function
|
'Seek'
|
|
|
|||
largeStep
|
number
|
—
|
|
|
|||
max
|
number
|
—
|
|
min
|
number
|
—
|
|
orientation
|
'horizontal' | 'vertical'
|
—
|
|
|
|||
step
|
number
|
—
|
|
|
|||
thumbAlignment
|
'center' | 'edge'
|
—
|
|
|
|||
value
|
number
|
—
|
|
State
render, className, and style props.
| Property | Type | Details |
|---|---|---|
bufferPercent
|
number
|
|
|
||
value
|
number
|
|
|
||
fillPercent
|
number
|
|
|
||
pointerPercent
|
number
|
|
|
||
dragging
|
boolean
|
|
|
||
pointing
|
boolean
|
|
|
||
interactive
|
boolean
|
|
|
||
orientation
|
'horizontal' | 'vertical'
|
|
|
||
disabled
|
boolean
|
|
|
||
thumbAlignment
|
'center' | 'edge'
|
|
|
||
currentTime
|
number
|
|
|
||
seeking
|
boolean
|
|
|
||
duration
|
number
|
|
|
||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-seeking
|
||
|
||
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
|
||
|
||
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
|
||
|
||