HLS and DASH
hls.js and dash.js load only when a source needs them. Quality menus work the same across both, and on plain MP4s.
React Video Kit 4 alpha
A React video player made of plugins. It starts at 6 KB, adds only what you use, and React doesn't render at all while the video plays.
Everything optional is a plugin. Fullscreen. Captions. HLS. DRM. Even hiding the controls. Import what you use, and the rest never reaches your bundle.
Plugins
Each switch rebuilds a real player and picks up from the same frame, so what you see is exactly what the code beside it ships.
About17.9KB of JavaScript, brotli
"use client";import { VideoPlayer } from "react-video-kit/player";import { idle } from "react-video-kit/idle";import { gestures } from "react-video-kit/gestures";import { hotkeys } from "react-video-kit/hotkeys";import { fullscreen } from "react-video-kit/fullscreen";import { speed } from "react-video-kit/speed";import "react-video-kit/styles.css";const plugins = [ idle(), gestures(), hotkeys(), fullscreen(), speed(),];export function Player() { return ( <VideoPlayer src="/video.mp4" plugins={plugins} /> );}Performance
A shared clock writes straight to the DOM, only when a pixel would change, and stops when playback does. Press play and leave your mouse alone. These counters are live, and the first one watches every React commit on this page.
React commits
0
Since you pressed play
Clock writes
0
To the DOM, no render
timeupdate
0
Fired by the browser
Composition
Start with a finished player today. Rearrange it next week. Replace every pixel when the design calls for it. Your plugins keep working at each step.
Every control, styled, in a layout that adapts from a phone to a cinema display.
"use client";import { defaultPlugins, VideoPlayer } from "react-video-kit/player";const plugins = defaultPlugins();export default function ComposeReady() { return ( <VideoPlayer src="/media/sample-720.mp4" poster="/media/poster.jpg" plugins={plugins} /> );}Arrange the same styled parts however you like. Here, the timeline moves into the control row.
"use client";import * as Video from "react-video-kit";import { fullscreen, Fullscreen } from "react-video-kit/fullscreen";import { idle } from "react-video-kit/idle";import { speed, SpeedMenu } from "react-video-kit/speed";const plugins = [idle(), speed(), fullscreen()];function SpeedTrigger() { const rate = Video.useVideoState((media) => media.rate); return <Video.MenuTrigger aria-label={`${rate}× speed`}>{rate}×</Video.MenuTrigger>;}export default function ComposeParts() { return ( <Video.Root src="/media/sample-720.mp4" plugins={plugins} load="visible"> <Video.Media poster="/media/poster.jpg" /> <Video.PlayOverlay /> <Video.Controls> <Video.ControlRow> <Video.ControlGroup> <Video.PlayPause /> <Video.Timeline /> <Video.Time /> </Video.ControlGroup> <Video.ControlGroup> <Video.Menu> <SpeedTrigger /> <Video.MenuContent> <SpeedMenu /> </Video.MenuContent> </Video.Menu> <Fullscreen /> </Video.ControlGroup> </Video.ControlRow> </Video.Controls> </Video.Root> );}Primitives bring focus, keyboard, ARIA, and state. This one is styled with nothing but Tailwind.
"use client";import * as Video from "react-video-kit/primitives";// No skin at all: the primitives bring behaviour and ARIA, and this file brings the look.export default function ComposeHeadless() { return ( <Video.Root src="/media/sample-720.mp4" load="visible" className="group relative aspect-video w-full overflow-hidden bg-black font-mono text-[#f6efe4]" > <Video.Media poster="/media/poster.jpg" className="block size-full object-cover" /> <div className="absolute inset-x-0 bottom-0 flex items-center gap-4 bg-gradient-to-t from-black/70 to-transparent px-5 pt-10 pb-4"> <Video.PlayPause className="group/play shrink-0 text-[13px] tracking-widest uppercase underline-offset-4 hover:underline"> <span className="group-data-[state=playing]/play:hidden">Play</span> <span className="hidden group-data-[state=playing]/play:inline">Pause</span> </Video.PlayPause> <Video.Timeline className="relative h-8 flex-1 cursor-pointer"> <Video.TimelineTrack className="absolute inset-x-0 top-1/2 h-px bg-white/40"> <Video.TimelineFill className="absolute inset-x-0 -top-px h-[3px] origin-left bg-[#ffb27a]" /> </Video.TimelineTrack> </Video.Timeline> <span className="shrink-0 text-[13px] tabular-nums"> <Video.CurrentTime /> / <Video.Duration /> </span> </div> </Video.Root> );}Theming
Start from a theme inspired by the platforms people already know, or set a few custom properties of your own. Your styles always win, and every control keeps working the same way.
/* The default look needs no theme. */<VideoPlayer src="/video.mp4" />Take a closer look
Adaptive streaming, live edges, ad breaks, and key systems. Each is a plugin behind the same small interface.
hls.js and dash.js load only when a source needs them. Quality menus work the same across both, and on plain MP4s.
A LIVE pill that knows when you've drifted from the edge, one click back, and a seekable window when the stream has one.
VAST with no SDK, or Google IMA. If an ad is blocked, slow, or broken, the content plays anyway.
Describe your licence servers once. It becomes the right settings for hls.js, dash.js, or native FairPlay.
The timeline, volume, and time all keep working while another screen does the playing.
Sliders are real range inputs, focus returns where it came from, and no shortcut fires while you're typing.
Try pressing one
Translated labels, plurals, and captions you can style. The layout mirrors in RTL, except the parts that shouldn't.
Real playback through real engines, every example checked for accessibility, server rendering run from the packed tarball, and tree-shaking proven in three bundlers.
Size
Every figure here comes from the package's own build, in brotli, without React. Each entry has a budget, and going over fails the check. Plugins you don't import add nothing.
pnpm add react-video-kit@alphaVersion 4 is in alpha, and its API can still change. It isn't on npm yet.