VideoPlayer
ImplementedVideo playback frame wrapping native <video controls> with aspect-ratio enforcement (16:9, 4:3, 1:1), poster, loading skeleton, and error fallback. Keyboard: Space (play/pause), ←/→ (seek ±5s), ↑/↓ (volume ±0.1), f (fullscreen), c (captions toggle — case-insensitive). prefers-reduced-motion guard blocks autoplay. Error state (role=alert) on playback failure.
patterns/molecules/videoplayer.htmlNative-controls video player with keyboard map and error fallback.
VideoPlayer
Media & playback — molecule
Native-controls video player with keyboard map and error fallback.
Usage
VideoPlayer is self-managing: give it a real src and an accessible label, and it handles the loading skeleton, the keyboard map and the error fallback itself. The native control bar (on by default) handles play, seek, volume and fullscreen;aspectRatio fixes the box shape and poster shows a frame before first load.
import { VideoPlayer } from '@epasslive/design-system'
// Default 16:9 with poster
<VideoPlayer
src="/media/trailer.mp4"
label="Event trailer"
poster="/media/trailer.jpg"
/>
// Square social embed (1:1), muted loop
<VideoPlayer
src={clip.url}
label={clip.title}
aspectRatio="1:1"
muted
loop
/>Keyboard
| Key | Action |
|---|---|
Space | Toggle play / pause (region focused) |
← | Seek back 5 seconds |
→ | Seek forward 5 seconds |
↑ | Volume up (+0.1) |
↓ | Volume down (−0.1) |
f | Toggle fullscreen (case-insensitive) |
c | Toggle first declared text track, if any (case-insensitive) |
Honest notes (current limitations)
- Autoplay policy rejection degrades to paused, not error. A
NotAllowedErrorfromplay()(browser blocking ungestured playback) leaves a healthy video paused on its poster — the error state is reserved for genuine media failures. Autoplay withmutedis safe to attempt; unmuted autoplay will typically just stay paused. No demo or story here uses autoplay. ccaptions toggle is near-inert. The key togglestextTracks[0], but only affects tracks embedded in the source file. The component renders no<track>element and offers no tracks prop — for ordinary sources,cis a no-op. WCAG 1.2.2 remains the consumer's responsibility.controls={false}leaves keyboard-only control with no visible play CTA. The molecule renders no play button. Keepcontrolson unless you provide your own chrome.- No custom scrub bar exists. Seeking is the native control bar or
←/→keys only. The molecule has no progress API and no timecode — live time and seeking are the browser's. - Hardcoded
playsInline+preload="metadata". Both are fixed behavior consumers cannot change via props. - The error fallback reflects genuine media failures. The third preview above uses a 404 source on purpose so the inline
role="alert"fallback renders for the right reason — never by autoplay (a policy-blocked autoplay now stays paused instead).
Props
src*stringVideo source URL passed to the native <video> element (HTTPS or same-app path).
label*stringAccessible name for the player region; also prefixes the error fallback text.
posterstringPoster frame URL shown before first load/play.
autoplaybooleanAttempt playback on mount. Suppressed when the user prefers reduced motion. Policy-blocked autoplay degrades to paused, not the error state — pair with muted for reliable ungestured autoplay. Default false.
mutedbooleanStart muted. Also the only autoplay mode browsers reliably allow ungestured. Default false.
controlsbooleanShow the native control bar. false leaves keyboard-only operation — the molecule renders no custom play UI. Default true.
loopbooleanReplay from the start when the video ends. Default false.
aspectRatio"16:9" | "4:3" | "1:1"Fixed box ratio (aspect-video / aspect-[4/3] / aspect-square), applied to both the frame and the error fallback. Default "16:9".
classNamestringAdditional classes on the root element.
idstringStable root id; falls back to an SSR-safe useId() value.
Props
VideoPlayer
srcstring—Video source URL (MP4, WebM, or HLS manifest). Required.
posterstring—Poster image URL shown before playback starts or while loading.
autoplaybooleanfalseAttempt autoplay on mount. Blocked by prefers-reduced-motion. Policy-blocked autoplay (NotAllowedError) degrades gracefully to paused — error state reserved for genuine media failures. Pair with muted=true for reliable autoplay.
mutedbooleanfalseStart muted. Required for autoplay to succeed under most browser policies.
controlsbooleantrueShow native video controls. When false, keyboard-only control (Space/arrows/f/c) with no visible play CTA.
loopbooleanfalseLoop playback continuously.
aspectRatioVideoAspectRatio"16:9"Aspect ratio: '16:9' (default), '4:3', or '1:1'. Enforced via CSS aspect-ratio.
labelstring—Accessible label for the video container (aria-label). Required for a11y.
classNamestring—Additional CSS classes appended to the wrapper.
idstring—Custom id; auto-generated via React.useId() if omitted.
<VideoPlayer />Accessibility
a11y
3 ruleslayout
1 ruleUsage
Import
import { VideoPlayer } from "@epasslive/design-system"Example 1
<VideoPlayer src="https://example.com/video.mp4" label="Product demo" />Example 2
<VideoPlayer src="https://example.com/video.mp4" poster="/poster.jpg" aspectRatio="1:1" label="Square preview" />Anti-patterns
— Do not use for audio-only content (use AudioPlayer)
— Do not set autoplay without muted — browsers block unmuted autoplay
— Do not expect custom scrub bar — seeking is native controls or arrow keys only