The headless
media player.
React Media Kit gives you the playback state, the keyboard behaviour and the accessibility work as unstyled parts. Compose them into a player that looks like your product.

What you get
Behaviour is hard. Styling should not be.
A set of compound components covers the full surface of a media element, handling the tricky behavioral details so you don't have to, and handing you the state to render however you want, with no markup or styling assumptions baked in.
No stylesheet ships with the kit. Parts render a bare element, forward your className and ref, and expose state as data attributes.
Playback, seeking, volume and fullscreen all respond to the shortcuts people already expect, with no extra wiring.
Components subscribe to only the slice of state they need, so updates stay fast no matter how deep the tree gets.
Dragging, a buffered track and clamped bounds are already handled, so you can focus purely on styling.
Cross-browser fullscreen and picture-in-picture come ready to use, with an active state you can style.
Tree-shakeable ES modules, zero runtime dependencies beyond React.
Anatomy
Compose your player, piece by piece
One root component sets up the shared player state. Everything else is optional, so you only compose the pieces you actually need.
Read the API referenceimport { Player, Video, Controls, Seekbar, PlayButton, TimeDisplay } from 'react-media-kit' function VideoPlayer({ src }) { return ( <Player.Root> <Player.Container> <Video.Root src={src} /> <Controls.Root> <Seekbar.Root> <Seekbar.Track> <Seekbar.Progress /> <Seekbar.Buffer /> <Seekbar.Thumb /> </Seekbar.Track> </Seekbar.Root> <PlayButton.Root>Play</PlayButton.Root> <TimeDisplay.Root> <TimeDisplay.Toggle> <TimeDisplay.Timer /> <span>/</span> <TimeDisplay.Duration /> </TimeDisplay.Toggle> </TimeDisplay.Root> </Controls.Root> </Player.Container> </Player.Root> ) }
import { useEffect, useRef } from 'react' import Hls from 'hls.js' import { Player, Video, Controls, PlayButton } from 'react-media-kit' function HlsPlayer({ src }) { const videoRef = useRef(null) useEffect(() => { const video = videoRef.current if (!video || !Hls.isSupported()) return const hls = new Hls() hls.loadSource(src) hls.attachMedia(video) return () => hls.destroy() }, [src]) return ( <Player.Root> <Player.Container> <Video.Root ref={videoRef} /> <Controls.Root> <PlayButton.Root>Play</PlayButton.Root> </Controls.Root> </Player.Container> </Player.Root> ) }
import { usePlayer, usePlayerControls, Seekbar, TimeDisplay } from 'react-media-kit' function CustomPlayButton() { const isPlaying = usePlayer((s) => s.isPlaying) const { toggle } = usePlayerControls() return ( <button onClick={toggle} className="rounded-full p-2 text-white/70 hover:text-white data-[playing]:text-emerald-400" > {isPlaying ? 'Pause' : 'Play'} </button> ) } function CustomSeekbar() { return ( <Seekbar.Root className="group relative h-1 w-full cursor-pointer"> <Seekbar.Track className="h-full rounded-full bg-white/15"> <Seekbar.Buffer className="h-full rounded-full bg-white/30" /> <Seekbar.Progress className="h-full rounded-full bg-emerald-400" /> <Seekbar.Thumb className="size-3 rounded-full bg-white opacity-0 transition group-hover:opacity-100 data-[dragging]:opacity-100" /> </Seekbar.Track> </Seekbar.Root> ) } function CustomTimeDisplay() { return ( <TimeDisplay.Root className="font-mono text-xs text-white/50 data-[live]:text-red-400"> <TimeDisplay.Toggle> <TimeDisplay.Timer /> <span>/</span> <TimeDisplay.Duration /> </TimeDisplay.Toggle> </TimeDisplay.Root> ) }
Hooks
Skip the parts entirely
If the compound components are more structure than you need, drop to the two hooks they're built on. Everything a player does is reachable from these.
Reads the live player state - playback status, current time, duration, buffered ranges, volume and rate. Pass a selector and the component re-renders only when that slice changes.
const isMuted = usePlayer((s) => s.isMuted) <span>{isMuted && "Muted"}</span>
Returns the imperative side: play, pause, seeking, volume and rate setters. The object identity is stable, so it's safe in dependency arrays and event handlers.
const { toggle, seek, setPlaybackRate } = usePlayerControls() <button onClick={toggle}>Play</button> <button onClick={() => seek(30)}>30s</button>
React Media Kit brings the behavior.
Bring your own styles.
MIT licensed, fully typed, and built for React 19+. Install it and start composing.