Video Player
ProA native way to play media: a real video element wrapped in a composable control surface. The scrubber and volume bar are Radix Sliders, and the controls auto-hide and stay legible over any frame.
Installation#
Usage#
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Anatomy#
The player is composed from named parts, never dot-notation. Drop in only the controls you need and arrange them however you like; every part reads the shared playback state from VideoPlayer's context, so order and grouping are entirely up to you.
import {
VideoPlayer,
Video,
VideoSpinner,
VideoControls,
VideoBar,
VideoPlayButton,
VideoSeek,
VideoTime,
VideoVolume,
VideoFullscreen,
} from "@/components/ui/video-player"
<VideoPlayer>
<Video />
<VideoSpinner />
<VideoControls>
<VideoBar>
<VideoPlayButton />
<VideoSeek />
<VideoTime />
<VideoVolume />
<VideoFullscreen />
</VideoBar>
</VideoControls>
</VideoPlayer>Minimal#
Nothing is required but VideoPlayer and Video. Here's a stripped-back bar with just play, scrubber, and timecode on a single row.
Scrub preview#
Hover (or drag) the scrubber and a timecode bubble rides the cursor, showing the moment you'd land on before you commit, so you can see where you're heading and, against the total in VideoTime, how much is left. It's built into VideoSeek, with no extra props. With preload="metadata" the duration is known up front, so it works before you ever press play.
Hover controls#
By default the controls stay up while the video is paused and auto-hide after an idle beat during playback. Pass revealOn="hover" for the cinematic treatment instead: the chrome is hidden until you point at the player, paused or playing alike, and reveals again on hover or when you tab into it (keyboard users are never stranded). The idle timer is skipped in this mode, visibility simply tracks the pointer.
It pairs naturally with the native <video> props that Video forwards: autoPlay, loop, and muted turn it into an ambient product loop with mute and zoom (fullscreen) one hover away. Browsers only allow autoplay on a muted video, so keep muted on whenever you set autoPlay.
Labels#
Every word the player speaks, from the play tooltip to the slider names a screen reader announces, comes from labels. Pass the keys you want to change; the rest keep their English defaults.
Controlled volume#
Pass volume and muted to own the level. The volume bar, the mute button and the arrow keys then show your values and report changes through onVolumeChange and onMutedChange, and the player stops writing volume or mute to the <video>. That is what lets you remember the level between visits, or play the sound somewhere else: audio tracks recorded apart from the picture and kept in sync with a video that stays muted. The demo keeps the level in state and applies it to the element itself.
Reaching the element#
A ref on Video reaches the real <video>; the player keeps its own ref alongside yours. Media handlers you pass (onPlay, onTimeUpdate, onVolumeChange and the rest) run after the player's own, so listening to the element never cuts the controls off from its state.
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Keyboard#
Click the video to focus the player, then drive it from the keyboard. The Radix scrubber and volume slider keep their own arrow-key handling when focused, so the shortcuts below only fire when the player frame itself holds focus.
| Key | Action |
|---|---|
| Space / K | Play / pause |
| ← / → | Seek 5 seconds |
| ↑ / ↓ | Volume up / down |
| M | Mute / unmute |
| F | Toggle fullscreen |
API reference#
VideoPlayer
The focusable frame that owns playback state (playing, time, volume, fullscreen, buffering) and shares it with every part through context. Forwards all native <div> props (its onVolumeChange is replaced by the one below); every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| revealOn | "auto" | "hover" | "auto" | auto keeps the controls up while paused and hides them after an idle beat during playback; hover shows them only while the pointer or focus is inside. |
| hideDelay | number | 2600 | Idle delay in ms before the controls auto-hide during playback (revealOn="auto" only). |
| labels | Partial<VideoPlayerLabels> | - | Tooltips and aria-labels: play, pause, seek, mute, unmute, volume, enterFullscreen, exitFullscreen, buffering. Missing keys keep the English defaults. |
| volume | number | - | Controlled volume, 0 to 1. Passing it hands the audio to you: the player stops writing volume or mute to the <video>. |
| onVolumeChange | (volume: number) => void | - | Called with the new level when the volume bar or an arrow key changes it. |
| muted | boolean | false | Controlled mute state, read only when volume is controlled. |
| onMutedChange | (muted: boolean) => void | - | Called when the mute button, the M key, or a drag to or from 0 flips the mute state. |
| Data attribute | Description |
|---|---|
| data-active | true while the controls are revealed, false while hidden (the cursor hides too). |
Video
The real <video> element; clicking it toggles play. Forwards all native <video> props, ref reaches the element, and your media handlers run after the player's own.
VideoControls
The bottom overlay with its own gradient scrim; fades in and out with the frame's data-active. Forwards all native <div> props.
VideoBar
One row of controls inside VideoControls. Forwards all native <div> props.
VideoPlayButton
Play and pause toggle with a rolling tooltip label; sets aria-pressed while playing. Forwards all native <button> props.
VideoSeek
The scrubber, a Radix Slider with a buffered-ahead fill and a hover timecode preview. Forwards all Radix Slider Root props except value and onValueChange; aria-label names the thumb (defaults to labels.seek).
| Data attribute | Description |
|---|---|
| data-disabled | Present until the duration is known (Radix Slider). |
VideoTime
Elapsed and total timecode, e.g. 0:42 / 3:15, in tabular-nums. Forwards all native <span> props.
VideoVolume
Mute toggle with a vertical Radix Slider flyout that opens on hover or focus and reads out the live percentage. Forwards all native <div> props.
VideoFullscreen
Enters and exits fullscreen on the frame; sets aria-pressed while fullscreen. Forwards all native <button> props.
VideoSpinner
A centered spinner that renders only while the media is stalled waiting for data. Forwards all native <div> props.