Composition
Three layers to build from. Start at the top, and go lower only when you need to.
All three use the same plugins and the same state, so you can move between them without rewriting anything else.
Every control, already arranged. Turn features on with plugins, and change the look with tokens.
"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} /> );}The same styled controls, in your own arrangement. Here, the timeline moves in line with the buttons.
0:000:00
"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> );}Behaviour and accessibility with no CSS at all. The whole design here comes from Tailwind classes in the example.
"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> );}Which one to use
| Choice | Ready-made | Styled parts | Headless |
|---|---|---|---|
| Import | react-video-kit/player | react-video-kit | react-video-kit/primitives |
| You write | One component | The layout | The layout and all CSS |
| Skin | Included | Included | None |
| Good for | Most players | A different layout | A design system of your own |
You can mix them too. Any part works inside VideoPlayer as a child, and your own components can read state with hooks at any layer.