media-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';
import './BasicUsage.css';
export default function BasicUsage() {
const [value, setValue] = useState(50);
return (
<div className="react-slider-basic">
<Slider.Root className="react-slider-basic__slider" value={value} onValueChange={setValue}>
<Slider.Track className="react-slider-basic__track">
<Slider.Fill className="react-slider-basic__fill" />
</Slider.Track>
<Slider.Thumb className="react-slider-basic__thumb" />
</Slider.Root>
</div>
);
}
.react-slider-basic {
display: flex;
align-items: center;
padding: 24px;
background: #1a1a1a;
}
.react-slider-basic__slider {
position: relative;
width: 100%;
display: flex;
align-items: center;
height: 20px;
cursor: pointer;
}
.react-slider-basic__track {
position: absolute;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.react-slider-basic__slider[data-interactive] .react-slider-basic__track {
height: 6px;
}
.react-slider-basic__fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-fill);
background: white;
border-radius: 9999px;
}
.react-slider-basic__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-slider-basic__slider[data-interactive] .react-slider-basic__thumb {
transform: translateX(-50%) scale(1);
}
.react-slider-basic__slider[data-dragging] .react-slider-basic__thumb {
transform: translateX(-50%) scale(1.1);
}
<div class="html-slider-basic">
<media-slider class="html-slider-basic__slider" value="50">
<media-slider-track class="html-slider-basic__track">
<media-slider-fill class="html-slider-basic__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="html-slider-basic__thumb"></media-slider-thumb>
</media-slider>
</div>
.html-slider-basic {
display: flex;
align-items: center;
padding: 24px;
background: #1a1a1a;
}
.html-slider-basic__slider {
position: relative;
width: 100%;
display: flex;
align-items: center;
height: 20px;
cursor: pointer;
}
.html-slider-basic__track {
position: absolute;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.html-slider-basic__slider[data-interactive] .html-slider-basic__track {
height: 6px;
}
.html-slider-basic__fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-fill);
background: white;
border-radius: 9999px;
}
.html-slider-basic__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-slider-basic__slider[data-interactive] .html-slider-basic__thumb {
transform: translateX(-50%) scale(1);
}
.html-slider-basic__slider[data-dragging] .html-slider-basic__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';
import './WithPreview.css';
export default function WithPreview() {
const [value, setValue] = useState(50);
return (
<div className="react-slider-preview">
<Slider.Root className="react-slider-preview__slider" value={value} onValueChange={setValue}>
<Slider.Track className="react-slider-preview__track">
<Slider.Fill className="react-slider-preview__fill" />
</Slider.Track>
<Slider.Thumb className="react-slider-preview__thumb" />
<Slider.Preview className="react-slider-preview__preview">
<Slider.Value type="pointer" className="react-slider-preview__value" />
</Slider.Preview>
</Slider.Root>
</div>
);
}
.react-slider-preview {
display: flex;
align-items: center;
padding: 40px 24px;
background: #1a1a1a;
}
.react-slider-preview__slider {
position: relative;
width: 100%;
display: flex;
align-items: center;
height: 20px;
cursor: pointer;
}
.react-slider-preview__track {
position: absolute;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.react-slider-preview__slider[data-interactive] .react-slider-preview__track {
height: 6px;
}
.react-slider-preview__fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-fill);
background: white;
border-radius: 9999px;
}
.react-slider-preview__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-slider-preview__slider[data-interactive] .react-slider-preview__thumb {
transform: translateX(-50%) scale(1);
}
.react-slider-preview__slider[data-dragging] .react-slider-preview__thumb {
transform: translateX(-50%) scale(1.1);
}
.react-slider-preview__preview {
position: absolute;
bottom: 100%;
margin-bottom: 6px;
pointer-events: none;
opacity: 0;
transition: opacity 150ms ease;
}
.react-slider-preview__slider[data-pointing] .react-slider-preview__preview {
opacity: 1;
}
.react-slider-preview__value {
background: rgba(0, 0, 0, 0.8);
color: white;
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
white-space: nowrap;
}
<div class="html-slider-preview">
<media-slider class="html-slider-preview__slider" value="50">
<media-slider-track class="html-slider-preview__track">
<media-slider-fill class="html-slider-preview__fill"></media-slider-fill>
</media-slider-track>
<media-slider-thumb class="html-slider-preview__thumb"></media-slider-thumb>
<media-slider-preview class="html-slider-preview__preview">
<media-slider-value type="pointer" class="html-slider-preview__value"></media-slider-value>
</media-slider-preview>
</media-slider>
</div>
.html-slider-preview {
display: flex;
align-items: center;
padding: 40px 24px;
background: #1a1a1a;
}
.html-slider-preview__slider {
position: relative;
width: 100%;
display: flex;
align-items: center;
height: 20px;
cursor: pointer;
}
.html-slider-preview__track {
position: absolute;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 9999px;
transition: height 150ms ease;
}
.html-slider-preview__slider[data-interactive] .html-slider-preview__track {
height: 6px;
}
.html-slider-preview__fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--media-slider-fill);
background: white;
border-radius: 9999px;
}
.html-slider-preview__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-slider-preview__slider[data-interactive] .html-slider-preview__thumb {
transform: translateX(-50%) scale(1);
}
.html-slider-preview__slider[data-dragging] .html-slider-preview__thumb {
transform: translateX(-50%) scale(1.1);
}
.html-slider-preview__preview {
position: absolute;
bottom: 100%;
margin-bottom: 6px;
pointer-events: none;
opacity: 0;
transition: opacity 150ms ease;
}
.html-slider-preview__slider[data-pointing] .html-slider-preview__preview {
opacity: 1;
}
.html-slider-preview__value {
background: rgba(0, 0, 0, 0.8);
color: white;
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
white-space: nowrap;
}
import '@videojs/html/ui/slider';
API Reference
Root media-slider
Props
| Prop | Type | Default | |
|---|---|---|---|
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 | |
|---|---|---|
value | number | |
| ||
fillPercent | number | |
| ||
pointerPercent | number | |
| ||
dragging | boolean | |
| ||
pointing | boolean | |
| ||
interactive | boolean | |
| ||
orientation | 'horizontal' | 'vertical' | |
| ||
disabled | boolean | |
| ||
thumbAlignment | 'center' | 'edge' | |
| ||
Data attributes
| Attribute | Type | |
|---|---|---|
data-dragging | ||
| ||
data-pointing | ||
| ||
data-interactive | ||
| ||
data-orientation | 'horizontal' | 'vertical' | |
| ||
data-disabled | ||
| ||
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 | ||
| ||
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 | |
|---|---|---|---|
format | ((value: number) => string) | — | |
| |||
type | "current" | "pointer" | — | |
| |||
Data attributes
| Attribute | Type | |
|---|---|---|
data-dragging | ||
| ||
data-pointing | ||
| ||
data-interactive | ||
| ||
data-orientation | 'horizontal' | 'vertical' | |
| ||
data-disabled | ||
| ||