Custom Video Player
A video player with a custom control bar that fades out automatically during playback, supporting scrub-to-seek, volume, fullscreen, and keyboard operation.
This is a WebberUI Pro component
The install command below needs a licence to run; without one it returns 401.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add "https://webberui.com/r/custom-video-player.en.json?t=<install token>"Custom Video Player is a React video player component that wraps the HTML5 <video> element with Tailwind CSS and Motion. It replaces the browser's default controls with a custom control bar that has scrub-to-seek, a buffered indicator, volume, fullscreen, and a full set of keyboard shortcuts.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<CustomVideoPlayer/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- Demo videos on a product homepage or feature page, where the controls should match the rest of the site.
- Screen recordings embedded in online courses or docs, where viewers scrub and skip with the keyboard a lot.
- Playing user-uploaded video inside an admin panel or SaaS app, including
MediaRecorderblobs whosedurationisInfinity. - Muted, looping background video on a landing page, using
autoPlay loop muted.
Installation
npx webberui@latest add "https://webberui.com/r/custom-video-player.en.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/custom-video-player.
Usage
import { CustomVideoPlayer } from "@/components/ui/custom-video-player";
<CustomVideoPlayer
src="/videos/demo.mp4"
poster="/videos/demo-poster.jpg"
className="w-full max-w-2xl"
/>Most browsers require muted alongside autoplay:
<CustomVideoPlayer src="/videos/loop.mp4" autoPlay loop muted />Examples
Sync playback with outside state
Use onPlayStateChange to pause other animations on the page while the video plays, or to send a view event.
"use client";
import * as React from "react";
import { CustomVideoPlayer } from "@/components/ui/custom-video-player";
export function ProductDemo() {
const [playing, setPlaying] = React.useState(false);
return (
<div className="space-y-2">
<CustomVideoPlayer
src="/videos/product-tour.mp4"
poster="/videos/product-tour.jpg"
label="Product tour video"
onPlayStateChange={setPlaying}
/>
<p className="text-sm text-muted-foreground">
{playing ? "Playing" : "Paused"}
</p>
</div>
);
}Vertical video with always-visible controls
Set aspectRatio to "9 / 16" and pass autoHide={false} so the control bar never fades out.
<CustomVideoPlayer
src="/videos/reel.mp4"
aspectRatio="9 / 16"
autoHide={false}
className="max-w-xs"
/>Play back a recorded blob
src accepts a URL from URL.createObjectURL; the component corrects the Infinity duration these blobs often report.
"use client";
import * as React from "react";
import { CustomVideoPlayer } from "@/components/ui/custom-video-player";
export function RecordingPreview({ blob }: { blob: Blob }) {
const [url, setUrl] = React.useState<string>();
React.useEffect(() => {
const next = URL.createObjectURL(blob);
setUrl(next);
return () => URL.revokeObjectURL(next);
}, [blob]);
if (!url) return null;
return <CustomVideoPlayer src={url} label="Recording preview" hideControlsDelay={1500} />;
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Video source URL |
poster | string | — | Cover image, shown before playback |
autoPlay | boolean | false | Autoplay (usually needs muted alongside it) |
loop | boolean | false | Loop playback |
muted | boolean | false | Whether it starts muted |
hideControlsDelay | number | 2400 | How many milliseconds of inactivity during playback before the control bar fades out |
autoHide | boolean | true | Whether the control bar fades out automatically |
aspectRatio | string | "16 / 9" | The container's CSS aspect-ratio |
label | string | "Video Player" (Video player) | Accessible label for the player container |
onPlayStateChange | (playing: boolean) => void | — | Callback when the playback state changes |
className | string | — | Appended to the container's className |
How it works
- The control bar fades out and hides the cursor while playing with no interaction, and reappears immediately on cursor movement, touch, focus, or a key press; it stays lit while paused or once playback ends.
- The progress bar can be clicked to seek and dragged to scrub (using pointer capture, so it keeps working even if the cursor leaves the player).
- Duration correction is built in: for streams or
MediaRecorderblobs whosedurationisInfinity, a recalculation is triggered automatically so the progress and the time display stay correct. - The volume slider is collapsed normally and expands when the mouse enters or focus lands on the volume area.
Accessibility
- When the user has "reduce motion" enabled at the system level, the control bar's automatic fade-out is disabled and it stays lit, with transition durations set to zero.
- Complete keyboard operation (once the player has focus):
Space/K: play/pause←/→(orJ/L): back/forward 5 seconds↑/↓: volume up/downHome/End: jump to the start/endM: toggle muteF: toggle fullscreen
- The progress bar is a
role="slider"witharia-valuemin/max/nowandaria-valuetext; every control button has anaria-label, and the mute and fullscreen buttons reflect their state witharia-pressed.
FAQ
Does it work with the Next.js App Router?
Yes. The source already starts with "use client", so import it from a Server Component page as usual; no extra wrapper is needed.
Why doesn't autoplay work?
Most browsers only allow muted video to autoplay, so pass muted together with autoPlay. The component also sets playsInline, so iOS Safari does not force fullscreen.
How do I change the fade-out delay or turn it off?
hideControlsDelay sets how many milliseconds of inactivity during playback pass before the control bar fades (default 2400), and autoHide={false} keeps it visible. The control bar also stays visible when the user has reduced motion enabled.
Does it download the whole video up front?
No. The <video> element uses preload="metadata", so only metadata such as duration loads at first; buffering starts once playback begins.
What are the dependencies?
motion, lucide-react, clsx, and tailwind-merge, plus cn from @/lib/utils. If you don't use the CLI, copy the source from the Manual tab.
Knowledge Graph
A force-directed knowledge graph built from Markdown or structured records: a constellation-style dark canvas you can drag, zoom and search, with each node's source citations in a side panel.
Audio Visualizer
Web Audio API audio spectrum visualization drawn in canvas 2D, in bar, mirror, and radial styles, able to analyze a real audio source or drive a decorative animation from synthetic data.