@wandelbots/video-player
v1.3.1
Published
Wandelbots Nova video player: framework-agnostic HTML5 video component with custom chrome, themed via @wandelbots/design-tokens.
Keywords
Readme
@wandelbots/video-player
Framework-agnostic HTML5 video player with custom Nova chrome. No MUI, no Tailwind, no streaming
library — a <video> element plus a themed control bar, so it drops into @wandelbots/mui and
@wandelbots/hui apps alike.
Install
pnpm add @wandelbots/video-playerUsage
import "@wandelbots/design-tokens/css"
import "@wandelbots/video-player/styles.css"
import { VideoPlayer } from "@wandelbots/video-player"
;<VideoPlayer
src="https://cdn.example.com/tutorial.mp4"
poster="https://cdn.example.com/tutorial.webp"
sources={[
{ label: "1080p", url: ".../1080p.mp4", width: 1920 },
{ label: "720p", url: ".../720p.mp4", width: 1280 },
{ label: "480p", url: ".../480p.mp4", width: 854 },
]}
/>Both CSS imports are required: @wandelbots/design-tokens/css supplies the --wb-* variables the
player's own --wbvp-* variables resolve against.
Props
| Prop | Type | Description |
| --- | --- | --- |
| src | string | Used when sources is empty; the renditions take over as soon as it is not. |
| sources | VideoSource[] | Selectable renditions, highest quality first (or annotated with width). |
| poster | string | Poster image. |
| captionUrl | string | WebVTT URL. An empty fallback track is rendered when omitted. |
| playerId | string | id on the root element. |
| className | string | Appended to the root class list. |
| borderless | boolean | Fills the container and drops the border, radius and shadow. |
| showSettings | boolean | Shows the Video Settings control for speed and quality selection. Defaults to true. |
| initialAspectRatio | number | Defaults to 16 / 9 until metadata loads. |
| labels | Partial<VideoPlayerLabels> | Overrides individual strings from the locale bundle. |
| locale | string | BCP-47 tag selecting a bundled language (en, de). Defaults to English. |
| icons | Partial<VideoPlayerIcons> | Overrides individual control icons. |
| onPlay / onPause / onEnded | () => void | Playback event callbacks. Internal source-swap pause/play events are excluded from onPlay and onPause. |
| onReady | (handle: VideoPlayerHandle) => void | Fires once on mount with a stable handle (seekToSeconds, scrollIntoView). A new callback identity does not refire it. |
| onAspectRatioChange | (ratio: number) => void | Fires once the intrinsic ratio is known, and again only if it changes. |
Video Settings
Video Settings includes playback speeds from 0.5x to 2x. The selected speed remains active
when changing renditions. Set showSettings={false} to hide the control entirely.
Quality selection
sources are sorted by descending width when annotated, otherwise used in the order given.
"Auto" picks the first entry above 1200px viewport width, the second above 900px, and the last
below that. Switching renditions preserves the current time and play state.
"Auto" re-evaluates whenever the viewport crosses one of those breakpoints, so resizing the window
can swap the rendition mid-playback. It is not bandwidth-aware and this is not adaptive bitrate —
for live or bandwidth-adaptive streams you want a different component. Picking a rendition by hand
pins it until the consumer remounts the player. Either way the swap is invisible to onPlay and
onPause, so analytics never see a resize as a fresh start.
Theming
Every color, radius and gradient is a --wbvp-* custom property declared on .wbvp-root, defaulting
to a --wb-zero-gravity-* token where one exists. Override any of them on your own wrapper:
.my-player {
--wbvp-letterbox: #000;
--wbvp-radius: var(--wb-radius-lg);
}The defaults assume the player sits on a dark surface: the control chrome, the letterbox fill and
the scrim gradients are all light-on-dark. On a light background, override at least
--wbvp-on-surface, --wbvp-letterbox and --wbvp-controls-gradient-*.
| Variable | Purpose |
| --- | --- |
| --wbvp-accent | Accent for the selected Video Settings option. |
| --wbvp-accent-hover | Hover tint for icon-only buttons. |
| --wbvp-accent-selected | Background of the selected quality option. |
| --wbvp-slider-rail | Unfilled seek and volume slider track. |
| --wbvp-slider-fill | Filled seek and volume slider track, and thumb border. |
| --wbvp-slider-thumb-background | Seek and volume slider thumb fill. |
| --wbvp-slider-radius | Seek and volume slider track and thumb radius. |
| --wbvp-slider-aura | Seek and volume slider thumb hover and active aura. |
| --wbvp-slider-focus-ring | Seek and volume slider thumb focus ring. |
| --wbvp-surface | Root background behind the frame. |
| --wbvp-border / --wbvp-radius / --wbvp-shadow | Framing of the root element. |
| --wbvp-on-surface | Control foreground: icons and time labels. |
| --wbvp-letterbox | Fill behind the video, visible when the ratio doesn't match. |
| --wbvp-fullscreen-backdrop | Frame background in fullscreen. |
| --wbvp-popover-bg / --wbvp-popover-border | Volume popover and quality menu. |
| --wbvp-btn-bg / --wbvp-btn-border / --wbvp-btn-border-strong | Round control buttons. |
| --wbvp-btn-hover-bg / --wbvp-btn-hover-border | Their hover state. |
| --wbvp-focus-ring | box-shadow applied on :focus-visible. |
| --wbvp-scrim-border / --wbvp-scrim-fill / --wbvp-scrim-hover-fill | Center play button. |
| --wbvp-controls-gradient-from / --wbvp-controls-gradient-to | Scrim behind the control bar. |
Accessibility
Every control carries an aria-label from labels, both sliders report an aria-valuetext
(1:35, 80%) instead of raw numbers, and a caption track is always present — an empty WebVTT
fallback when captionUrl is omitted. The quality menu is a role="menu" of menuitemradio
options with arrow-key, Home/End and Escape handling, and it hands focus back to its trigger when
it closes.
Once the player surface has focus — click the video, or focus any control — these shortcuts apply:
| Key | Action |
| --- | --- |
| Space / k | Play or pause |
| ← / → | Skip 5 seconds |
| ↑ / ↓ | Volume ±10% |
| m | Mute or unmute |
| f | Toggle fullscreen |
Keys a focused control needs itself are left alone: arrows adjust the slider you are on, Space
activates the button you are on, and the quality menu keeps its own arrow keys while open.
prefers-reduced-motion: reduce disables the control transitions and the smooth scroll performed
by scrollIntoView().
Browser support
Current Chrome, Edge, Firefox and Safari, plus iOS Safari. Requires color-mix() and CSS custom
properties.
iPhone Safari implements no element fullscreen, so the fullscreen button hands the video to the native iOS player instead; the custom chrome is not shown in that mode. Where neither element fullscreen nor the iOS fallback exists, the button is not rendered at all rather than shipping a dead control.
Playback is progressive HTTP: whatever formats the browser itself supports, typically MP4/H.264 everywhere and WebM outside Safari.
Internationalization
The player ships its own strings for English and German, so most consumers only pick a language:
<VideoPlayer src={src} locale="de" />locale takes any BCP-47 tag. The region subtag is ignored (de-AT uses the German bundle) and a
language the package has no strings for falls back to English, so passing whatever your i18n runtime
holds is safe. LOCALES exports the bundles if you want to read or extend them.
There is still no i18n runtime in the package. For a language it doesn't ship, or to change single
strings, pass labels — it merges over the locale bundle, so a partial override keeps the rest of
the chosen language:
<VideoPlayer src={src} locale="de" labels={{ play: "Film starten" }} />captionsSrcLang and captionsLabel describe the caption file, not the UI. They follow locale,
which is right when captions match the interface language — override them through labels when it
doesn't.
