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.
Seeking is throttled via the commitThrottle prop (default 100ms) to avoid overwhelming the media element during drag operations.
Styling
Use CSS custom properties to style the fill, pointer, and buffer levels:
media-time-slider::before {
width: calc(var(--media-slider-fill) * 1%);
} Use data-seeking to style during active seek operations:
media-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';
import './WithParts.css';
const Player = createPlayer({ features: videoFeatures });
export default function WithParts() {
return (
<Player.Provider>
<Player.Container className="react-time-slider-parts">
<Video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<TimeSlider.Root className="react-time-slider-parts__slider">
<TimeSlider.Track className="react-time-slider-parts__track">
<TimeSlider.Buffer className="react-time-slider-parts__buffer" />
<TimeSlider.Fill className="react-time-slider-parts__fill" />
</TimeSlider.Track>
<TimeSlider.Thumb className="react-time-slider-parts__thumb" />
<TimeSlider.Value type="pointer" className="react-time-slider-parts__value" />
</TimeSlider.Root>
</Player.Container>
</Player.Provider>
);
}
.react-time-slider-parts {
position: relative;
}
.react-time-slider-parts video {
width: 100%;
}
.react-time-slider-parts__slider {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
height: 20px;
cursor: pointer;
}
.react-time-slider-parts__track {
position: absolute;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.react-time-slider-parts__slider[data-interactive] .react-time-slider-parts__track {
height: 6px;
}
.react-time-slider-parts__buffer {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-buffer);
background: rgba(255, 255, 255, 0.4);
border-radius: 9999px;
}
.react-time-slider-parts__fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-fill);
background: white;
border-radius: 9999px;
}
.react-time-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-time-slider-parts__slider[data-interactive] .react-time-slider-parts__thumb {
transform: translateX(-50%) scale(1);
}
.react-time-slider-parts__slider[data-dragging] .react-time-slider-parts__thumb {
transform: translateX(-50%) scale(1.1);
}
.react-time-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-time-slider-parts__slider[data-pointing] .react-time-slider-parts__value {
opacity: 1;
}
<video-player class="html-time-slider-parts">
<media-container>
<video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<media-time-slider class="html-time-slider-parts__slider">
<media-slider-track class="html-time-slider-parts__track">
<media-slider-buffer class="html-time-slider-parts__buffer"></media-slider-buffer>
<media-slider-fill class="html-time-slider-parts__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="html-time-slider-parts__thumb"></media-slider-thumb>
<media-slider-value type="pointer" class="html-time-slider-parts__value"></media-slider-value>
</media-time-slider>
</media-container>
</video-player>
.html-time-slider-parts,
.html-time-slider-parts media-container {
display: block;
position: relative;
}
.html-time-slider-parts video {
width: 100%;
}
.html-time-slider-parts__slider {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
height: 20px;
cursor: pointer;
}
.html-time-slider-parts__track {
position: absolute;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.html-time-slider-parts__slider[data-interactive] .html-time-slider-parts__track {
height: 6px;
}
.html-time-slider-parts__buffer {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-buffer);
background: rgba(255, 255, 255, 0.4);
border-radius: 9999px;
}
.html-time-slider-parts__fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-fill);
background: white;
border-radius: 9999px;
}
.html-time-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-time-slider-parts__slider[data-interactive] .html-time-slider-parts__thumb {
transform: translateX(-50%) scale(1);
}
.html-time-slider-parts__slider[data-dragging] .html-time-slider-parts__thumb {
transform: translateX(-50%) scale(1.1);
}
.html-time-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-time-slider-parts__slider[data-pointing] .html-time-slider-parts__value {
opacity: 1;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/time-slider';
API Reference
Root media-time-slider
Props
| Prop | Type | Default | |
|---|---|---|---|
commitThrottle | 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 | |
|---|---|---|
bufferPercent | number | |
| ||
value | number | |
| ||
fillPercent | number | |
| ||
pointerPercent | number | |
| ||
dragging | boolean | |
| ||
pointing | boolean | |
| ||
interactive | boolean | |
| ||
orientation | 'horizontal' | 'vertical' | |
| ||
disabled | boolean | |
| ||
thumbAlignment | 'center' | 'edge' | |
| ||
duration | number | |
| ||
currentTime | number | |
| ||
seeking | boolean | |
| ||
Data attributes
| Attribute | Type | |
|---|---|---|
data-seeking | ||
| ||
CSS custom properties
| Variable | |
|---|---|
--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 | |
|---|---|---|---|
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 | ||
| ||