Consistent controls for self-hosted video (play, seek, time, volume, captions, speed, picture-in-picture, full screen) with full keyboard support; a privacy-friendly lite embed for YouTube and Vimeo; and a decorative background video with a pause button. HLS loads hls.js only when it is needed.
Space or K to play · ← → seek 5 s · ↑ ↓ volume · M mute · F full screen
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { VideoPlayer, VideoEmbed } from "@vamos-ui/react/media";
<VideoPlayer src="/media/onboarding.mp4" poster="/media/onboarding.jpg" title="Onboarding: your first week" />
<VideoEmbed provider="youtube" id="aqz-KE-bpKQ" title="Product tour" />Keys (focus inside the player): Space / K play, ← → 5 s, ↑ ↓ volume, M mute, C captions, F full screen.
The chrome is always dark: video content is treated as dark in every theme. Progress uses --accent. The seek bar stays left-to-right in RTL.
Examples
Captions and subtitles (HR training)
WebVTT tracks; C toggles captions. chrome="bar" keeps the controls visible.
YouTube / Vimeo lite embed
A poster and a play button until clicked, then a youtube-nocookie.com (or Vimeo dnt=1) iframe. No third-party request before consent.
Background video (website hero)
Muted, looping, aria-hidden, with a visible pause button (WCAG 2.2.2). Reduced motion shows the poster only.
Kalamış Bistro
The courtyard opens, every evening from six
HLS stream
Pass hls={() => import("hls.js")}; Safari plays HLS natively and never loads it.
API
VideoPlayer
| Prop | Type | Default |
|---|---|---|
srcOne file, an asset, or several sources. .m3u8 is detected as HLS. | string | MediaAsset | { url; type: "mp4" | "webm" | "hls" }[] | — |
posterShown before playback. | string | MediaAsset | — |
titleAccessible name ("Video player: {title}") and the play button label. | string | — |
aspectBox ratio. | ImageAspect | asset ratio, else 16/9 |
tracksWebVTT captions / subtitles. | { src; lang; label; kind?; default? }[] | — |
controlsKit controls, browser controls, or none. | "custom" | "native" | "none" | "custom" |
chromeoverlay hides while playing and idle; bar stays visible. | "overlay" | "bar" | "overlay" |
roundedCorners. | "none" | "ui" | "ui-lg" | "ui" |
autoPlay / muted / loop / playsInlineNative flags. autoPlay is ignored under reduced motion. | boolean | false / false / false / true |
preloadNative preload. | "none" | "metadata" | "auto" | "metadata" |
hlsLazy hls.js loader for browsers without native HLS. | () => Promise<{ default: Hls }> | — |
onEnded / onTimeUpdatePlayback callbacks. | () => void / (t: number) => void | — |
videoRefThe underlying element. | Ref<HTMLVideoElement> | — |
labelsplay, pause, seek, mute, captionsOn, speed… (dictionary namespace videoPlayer). | Partial<VideoPlayerLabels> | — |
VideoEmbed
| Prop | Type | Default |
|---|---|---|
providerWhere the video lives. | "youtube" | "vimeo" | — |
idVideo id. | string | — |
titleRequired: names the play button and the iframe. | string | — |
posterFacade image (needed for Vimeo). | string | YouTube thumbnail |
startStart at this second. | number | — |
aspect / roundedBox. | ImageAspect / string | "16/9" / "ui" |
BackgroundVideo
| Prop | Type | Default |
|---|---|---|
srcMuted, looping source. | string | { url; type }[] | — |
posterRequired: first frame, and the only image under reduced motion. | string | — |
childrenContent on top. | ReactNode | — |