Headless media primitives for React

This library is experimental. The API is not stable yet and may change between minor releases. Not recommended for production use.

The headless
media player.

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

Get started
Space play/pause · ← → seek · M mute · F fullscreen

What you get

Behavior 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.

Headless by default

No stylesheet ships with the kit. Parts render a bare element, forward your className and ref, and expose state as data attributes.

Keyboard shortcuts built in

Playback, seeking, volume and fullscreen all respond to the shortcuts people already expect, with no extra wiring.

One external store

Components subscribe to only the slice of state they need, so updates stay fast no matter how deep the tree gets.

Seeking that feels right

Dragging, a buffered track and clamped bounds are already handled, so you can focus purely on styling.

Features detection

State exposes which fullscreen, picture-in-picture and other browser features are actually supported.

Small and quiet

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 reference
Player.tsx
import { 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 { 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>
  )
}

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.

usePlayer()

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.

isPlayingcurrentTimedurationbufferedvolumeisMutedplaybackRate
const isMuted = usePlayer((s) => s.isMuted)

<span>{isMuted && "Muted"}</span>
usePlayerControls()

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.

toggleseekskiptoggleMutetoggleFullscreensetPlaybackRate
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.