8.3 KiB
status, date
| status | date |
|---|---|
| draft | 2026-02-09 |
Slider
Compound, headless slider components for media controls — seek, volume, and future use cases.
Contents
| Document | Purpose |
|---|---|
| index.md | Overview, anatomy, quick start |
| architecture.md | Core classes, DOM interaction, file structure |
| parts.md | All compound parts — props, state, data attributes |
| decisions.md | Design decisions and rationale |
Problem
Media players need sliders for two core interactions:
- Time seek — scrub through video, see buffered range, preview time at pointer
- Volume — adjust volume level, often vertical
Both share mechanics (drag, keyboard, pointer tracking) but differ in what they control and what they display. A time slider needs buffered progress, formatted time values, and thumbnail previews. A volume slider just needs a fill bar.
Requirements:
- Compound and composable — users assemble parts, omit what they don't need
- Headless — no baked-in styles, CSS custom properties for positioning
- Accessible —
role="slider", full keyboard support,aria-valuetext - Treeshakeable — domain sliders (time, volume) import only what they use
- Cross-platform — same core logic drives React components and HTML custom elements
Anatomy
React
import { TimeSlider } from '@videojs/react';
<TimeSlider.Root>
<TimeSlider.Track>
<TimeSlider.Buffer />
<TimeSlider.Fill />
</TimeSlider.Track>
<TimeSlider.Thumb />
<TimeSlider.Preview>
<TimeSlider.Value type="pointer" />
</TimeSlider.Preview>
</TimeSlider.Root>
import { VolumeSlider } from '@videojs/react';
<VolumeSlider.Root>
<VolumeSlider.Track>
<VolumeSlider.Fill />
</VolumeSlider.Track>
<VolumeSlider.Thumb />
</VolumeSlider.Root>
Generic parts are also available via import { Slider } for building custom slider types.
HTML
import '@videojs/html/ui/time-slider';
import '@videojs/html/ui/volume-slider';
import '@videojs/html/ui/slider-preview'; // opt-in, separate from basic parts
<media-time-slider>
<media-slider-track>
<media-slider-buffer></media-slider-buffer>
<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-time-slider>
<media-volume-slider orientation="vertical">
<media-slider-track>
<media-slider-fill></media-slider-fill>
</media-slider-track>
<media-slider-thumb></media-slider-thumb>
</media-volume-slider>
Layers
Three layers, each independently useful:
| Layer | Package | Purpose |
|---|---|---|
| Core | @videojs/core |
State computation, ARIA attrs, raw percentages. Accepts split (interaction, media) inputs. No DOM. |
| DOM | @videojs/core/dom |
Pointer/keyboard interaction (createSlider), interaction state (createState), CSS var formatting (getSliderCSSVars). |
| UI | @videojs/react, @videojs/html |
Compound components and custom elements. HTML elements dispatch custom DOM events. |
See architecture.md for internals.
CSS Custom Properties
Sliders expose continuous values as CSS custom properties on the root element. Users style parts using these — no inline styles are applied.
| Property | Example | Description |
|---|---|---|
--media-slider-fill |
45.000% |
Current value as percentage of range |
--media-slider-pointer |
62.500% |
Pointer position as percentage of track |
--media-slider-buffer |
78.000% |
Buffered range as percentage (set by domain roots that have a buffer concept) |
/* Fill bar width follows the value */
media-slider-fill {
width: var(--media-slider-fill, 0%);
}
/* Thumb position tracks the value */
media-slider-thumb {
left: var(--media-slider-fill, 0%);
}
/* Preview follows the pointer */
media-slider-preview {
left: var(--media-slider-pointer, 0%);
}
/* Buffered range */
media-slider-buffer {
width: var(--media-slider-buffer, 0%);
}
Data Attributes
State is exposed through data attributes for CSS targeting. Applied to the root element and all children (Track, Fill, Buffer, Thumb, Preview, Value).
| Attribute | Values | When |
|---|---|---|
data-dragging |
present/absent | User is dragging the slider |
data-pointing |
present/absent | Pointer is over the slider |
data-interactive |
present/absent | Hovering, focused, or dragging |
data-orientation |
horizontal / vertical |
Always present |
data-disabled |
present/absent | Slider is disabled |
Time slider adds:
| Attribute | Values | When |
|---|---|---|
data-seeking |
present/absent | Media is seeking |
/* Show preview only when interactive (element-level selector — attrs inherited) */
media-slider-preview:not([data-interactive]) {
display: none;
}
/* Disabled state */
media-time-slider[data-disabled] {
opacity: 0.5;
pointer-events: none;
}
/* Enlarge thumb while dragging (element-level — no ancestor selector needed) */
media-slider-thumb[data-dragging] {
transform: scale(1.2);
}
Keyboard
Keyboard events are handled by the Thumb element (the focusable role="slider" element). Defaults match YouTube's seek behavior. All step values are customizable via props.
| Key | Time Slider | Volume Slider |
|---|---|---|
ArrowRight / ArrowUp |
Seek forward 5 seconds | Increase volume 5% |
ArrowLeft / ArrowDown |
Seek backward 5 seconds | Decrease volume 5% |
Shift + Arrow |
Seek forward/backward 10 seconds | Increase/decrease volume 10% |
PageUp |
Seek forward 10 seconds | Increase volume 10% |
PageDown |
Seek backward 10 seconds | Decrease volume 10% |
Home |
Seek to beginning | Set to minimum |
End |
Seek to end | Set to maximum |
0–9 |
Jump to 0%–90% of duration | Jump to 0%–90% of range |
Default step values:
- Time slider:
step = 1(second),largeStep = 10(seconds) - Volume slider:
step = 1(%),largeStep = 10(%)
Customizable via step and largeStep props on the Root.
Accessibility
The Thumb element carries the slider ARIA role and is the keyboard focus target. Root handles pointer events (click-to-seek on track area). This follows the WAI-ARIA Slider Pattern and the Media Seek Slider Example.
Thumb is always present in the DOM for accessibility. Users who want a "thumbless" slider hide it visually with CSS — the element remains focusable and announced by screen readers.
<media-time-slider>
<!-- Root: pointer events, CSS vars, data attrs -->
<media-slider-track>...</media-slider-track>
<media-slider-thumb
role="slider"
tabindex="0"
aria-label="Seek"
aria-valuemin="0"
aria-valuemax="600"
aria-valuenow="150"
aria-valuetext="2 minutes, 30 seconds of 10 minutes"
aria-orientation="horizontal">
</media-slider-thumb>
</media-time-slider>
Volume slider:
<media-volume-slider orientation="vertical">
<media-slider-track>...</media-slider-track>
<media-slider-thumb
role="slider"
tabindex="0"
aria-label="Volume"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="75"
aria-valuetext="75 percent"
aria-orientation="vertical">
</media-slider-thumb>
</media-volume-slider>
aria-valuetext includes the maximum value on initialization and when the thumb receives focus, but not on every value change — avoids screen reader verbosity (per APG seek slider guidance).
Related Docs
- architecture.md — Core classes, file structure, data flow
- parts.md — Full API for every compound part
- decisions.md — Design rationale
- Time Display design — Related compound component