Video player

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.

Native <video> · hls.js (optional, lazy) media/video-player.tsxtheme · material · accent · density · font

Space or K to play · ← → seek 5 s · ↑ ↓ volume · M mute · F full screen

Installation

$ pnpm add @vamos-ui/react

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.

0:000:00

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

PropTypeDefault
src
One file, an asset, or several sources. .m3u8 is detected as HLS.
string | MediaAsset | { url; type: "mp4" | "webm" | "hls" }[]—
poster
Shown before playback.
string | MediaAsset—
title
Accessible name ("Video player: {title}") and the play button label.
string—
aspect
Box ratio.
ImageAspectasset ratio, else 16/9
tracks
WebVTT captions / subtitles.
{ src; lang; label; kind?; default? }[]—
controls
Kit controls, browser controls, or none.
"custom" | "native" | "none""custom"
chrome
overlay hides while playing and idle; bar stays visible.
"overlay" | "bar""overlay"
rounded
Corners.
"none" | "ui" | "ui-lg""ui"
autoPlay / muted / loop / playsInline
Native flags. autoPlay is ignored under reduced motion.
booleanfalse / false / false / true
preload
Native preload.
"none" | "metadata" | "auto""metadata"
hls
Lazy hls.js loader for browsers without native HLS.
() => Promise<{ default: Hls }>—
onEnded / onTimeUpdate
Playback callbacks.
() => void / (t: number) => void—
videoRef
The underlying element.
Ref<HTMLVideoElement>—
labels
play, pause, seek, mute, captionsOn, speed… (dictionary namespace videoPlayer).
Partial<VideoPlayerLabels>—

VideoEmbed

PropTypeDefault
provider
Where the video lives.
"youtube" | "vimeo"—
id
Video id.
string—
title
Required: names the play button and the iframe.
string—
poster
Facade image (needed for Vimeo).
stringYouTube thumbnail
start
Start at this second.
number—
aspect / rounded
Box.
ImageAspect / string"16/9" / "ui"

BackgroundVideo

PropTypeDefault
src
Muted, looping source.
string | { url; type }[]—
poster
Required: first frame, and the only image under reduced motion.
string—
children
Content on top.
ReactNode—