Video Player React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React video player with controls that hide while you watch, a chapter scrubber, captions, speed, picture in picture and full screen.
Click or tap the big play button (or anywhere on the picture) to start; with a mouse, a click on the picture plays and pauses and a double click goes full screen. While it plays, the controls and the title fade away after 2.5 s of stillness and come back when you move the mouse, tap or focus a control; the captions glide down with them. Hover the scrubber and it thickens, a thumb appears and a pill shows the time and the chapter under the pointer; press and drag to scrub (a mouse seeks as it moves, a finger seeks on release), chapters are small ticks on the bar. The row has play, skip back / forward 10 s, mute with a volume slider, the time, a captions button (a menu when there are several languages), a speed menu, picture in picture and full screen; on phones the menus open as bottom sheets. On a touch screen a tap shows or hides the controls, a tap on the middle of the picture plays and pauses while they show, the big button is flanked by skip buttons, and a double tap on the left or right third jumps 10 s with a ripple and a pill that counts up as you keep tapping. Keyboard (focus the player or any control): Space or k play and pause, Left / Right seek 5 s, j / l 10 s, Up / Down volume, m mute, 0 to 9 jump to 0% to 90%, Home / End, < and > change the speed, f full screen, c captions on or off, i picture in picture; on the focused scrubber Left / Right seek 5 s, PageUp / PageDown 30 s. While it plays in picture in picture the stage says so and the bar stays usable. A missing file shows a message with a retry button.
Video Player is a Pro React component in the React media components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add video-player, together with Button, Dropdown Menu, Slider, Spinner, Tooltip and 5 more and the npm packages motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add video-playerVideo Player is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import VideoPlayer from @/components/ui/video-player.
import { VideoPlayer } from "@/components/ui/video-player"
export function CityTour() {
return (
<VideoPlayer
src="/demo/city-tour.mp4"
poster="/demo/city-tour.jpg"
title="City tour"
tracks={[
{ src: "/demo/city-tour.en.vtt", srclang: "en", label: "English", default: true },
{ src: "/demo/city-tour.ro.vtt", srclang: "ro", label: "Romanian" },
]}
chapters={[
{ start: 0, title: "The main boulevard" },
{ start: 7.8, title: "The mountains" },
{ start: 11, title: "Home" },
{ start: 16, title: "Final" },
]}
/>
)
}Props
src*string | VideoSource[]The video url, or several { src, type } sources (webm, mp4) for the browser to pick from.
posterdefault""stringThe still shown before the first play (it fades out on play). Also the lock-screen artwork.
titledefault""ReactNodeA title in a top scrim while the controls show; when it is a string, also the region name and the lock-screen title.
tracksdefault"[]"VideoTrack[]WebVTT captions { src, srclang, label, kind?, default? }. The native tracks stay hidden and the player draws the active cue itself, above the controls. Tracks on another origin need CORS (the video gets crossOrigin anonymous).
chaptersVideoChapter[]{ start, title } in seconds: ticks on the scrubber, the chapter in the time pill and next to the time on wide players.
aspectRatiodefault"16 / 9"stringCSS aspect ratio of the stage, e.g. 1 / 1 for a listing card or 9 / 16 for a story.
fitdefault"contain""contain" | "cover"contain letterboxes the whole picture on black, cover crops it to fill the stage.
radiusdefault"md""none" | "sm" | "md" | "lg"Corner radius of the stage (0 / 12 / 16 / 24px). Full screen drops the corners.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Colors the played part of the scrubber and the captions-on underline. neutral is white on the black stage in both themes.
colordefault""stringAny CSS color for the played bar; replaces the tone.
captionSizedefault"md""sm" | "md" | "lg"Caption text size. Each step also grows with the player's own width, so a card and a full-screen player both read well.
controlsdefault"auto""auto" | "always" | "none"auto hides the controls while playing and idle for hideDelay (paused, a menu open, keyboard focus or scrubbing keep them up); always keeps them; none shows only the picture, for background clips.
hideDelaydefault2500numberMilliseconds of stillness before the controls hide while playing. The mouse cursor hides with them.
skipSecondsdefault10numberSeconds for the skip buttons, a double tap and j / l. Arrow keys seek half of it.
doubleTapSeekdefaulttruebooleanTouch: a double tap on the left or right third seeks by skipSeconds; more taps keep adding and the pill counts.
pipdefaulttruebooleanThe picture in picture button (and the i key) where the browser supports it. While it is out, the stage says so.
fullscreendefaulttruebooleanThe full screen button, the f key and double click. The whole player goes full screen, so the controls and captions stay ours; phones try to turn to landscape. iOS Safari falls back to its native player.
autoPlaydefaultfalsebooleanStarts muted (browsers require it), pauses while scrolled out of view and resumes when back, unless the viewer paused it.
muteddefaultfalsebooleanStarts without sound; the mute button stays usable.
loopdefaultfalsebooleanStarts again from the beginning when it ends.
startTimedefault0numberSeconds to start from once the metadata loads, for continue watching. 0 starts at the beginning.
exclusivedefaulttruebooleanStarting this player pauses every other exclusive player and audio player on the page.
mediaSessiondefaulttruebooleanWhile playing, sets the lock-screen title and artwork and handles play, pause and seek from the lock screen and headphones.
preloaddefault"metadata""none" | "metadata" | "auto"How much the browser loads before play. metadata knows the duration without downloading the film.
captionsDefaultdefault""string | "off"The caption language on at start (a track srclang), or "off". Empty uses the track marked default, else off.
ratesdefault"MEDIA_RATES"readonly number[]Speeds in the speed menu (0.5 to 2 by default). One rate hides the menu.
onPlay() => voidCalled when playback starts.
onPause() => voidCalled when playback pauses, including the automatic pause of autoPlay off screen.
onEnded() => voidCalled at the end (never while looping).
onError(error: MediaError | null) => voidCalled when the video cannot be loaded or played.
onTimeUpdate(seconds: number) => voidWhole seconds while playing and after seeks, for saving progress.
setApi(api: VideoPlayerApi) => voidThe media controls (play, pause, seek, second, duration, progress…) plus fullscreen, pip, captions, enterFullscreen(), exitFullscreen(), togglePip() and setCaptions(lang | "off"). Called after mount and whenever the state changes.
labelsPartial<VideoPlayerLabels>Every visible and spoken string, English by default: play, pause, replay, seek, skipBack(s), skipForward(s), skippedBack(s), skippedForward(s), volume, mute, unmute, captions, captionsOff, speed, normal, rate(r), pip, exitPip, pipActive, fullscreen, exitFullscreen, error, retry, cancel (the phone sheet button), live, loading, video, position(current, total).
classNamesPartial<Record<"video" | "poster" | "bigPlay" | "spinner" | "captions" | "cue" | "ripple" | "center" | "skip" | "pip" | "title" | "controls" | "scrubber" | "track" | "thumb" | "bubble" | "row" | "button" | "time" | "error" | "live", string>>Extra classes for the inner parts.
classNamestringExtra classes for the stage, merged last.
Parts
DEFAULT_VIDEO_PLAYER_LABELS
constantThe English strings for every button, the scrubber, captions, speed, picture in picture and the screen reader position. Spread it and override a few keys through labels.
VideoPlayerApi
typeWhat setApi hands you: the media controls plus fullscreen, pip, captions and the methods to toggle them.
VideoTrack
typeA captions or subtitles file for tracks: { src, srclang, label, kind?: "subtitles" | "captions", default? }.
VideoChapter
typeOne chapter for the scrubber: { start (seconds), title }.
VideoPlayerProps
typeThe props of VideoPlayer, for wrappers that pass them through.
Examples
Hero background
autoPlay, muted, loop and no controls behind a headline; it pauses when scrolled out of view.
Large captions
English captions on from the start at captionSize lg, drawn above the controls.
Listing video card
A square cover crop in a listing card, three speeds and no picture in picture.
On a phone
Tap for controls, double tap left or right to skip; speed and captions open as bottom sheets.
Chapters with setApi
An outside chapter list that seeks the player and follows the playhead.
Brand tone, resume position
tone brand colors the played bar; startTime resumes at 0:08 like continue watching.
Error
A missing file shows the message and a retry button.
Dependencies
Files
- components/ui/video-player.tsx
FAQ
Is the React Video Player component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Video Player?
Run npx wingo-ui@latest add video-player. It also adds the Wingo UI items it builds on (Button, Dropdown Menu, Slider, Spinner, Tooltip and 5 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/video-player.
How does Video Player behave on phones?
The row has play, skip back / forward 10 s, mute with a volume slider, the time, a captions button (a menu when there are several languages), a speed menu, picture in picture and full screen; on phones the menus open as bottom sheets.
