Seekbar

A timeline that shows how much of the media has played and buffered. Click, drag or use the keyboard to seek.

import { Seekbar } from 'react-media-kit'
import { Seekbar } from 'react-media-kit'
import './timeline.css'

export function Timeline() {
  return (
    <Seekbar.Root className="seekbar" skipInterval={10}>
      <Seekbar.Track className="seekbar-track">
        <Seekbar.Buffer className="seekbar-buffer" />
        <Seekbar.Progress className="seekbar-progress" />
        <Seekbar.Thumb className="seekbar-thumb" />
      </Seekbar.Track>
    </Seekbar.Root>
  )
}

Render these inside Player.Root, next to a Media.Video or Media.Audio.

Seekbar.Buffer and Seekbar.Progress set their own width from --buffer-percent and --progress-percent, and the thumb positions itself, so your CSS only handles how they look. The track still needs a size.

Anatomy

How the parts nest.

<Seekbar.Root>
  <Seekbar.Track>
    <Seekbar.Buffer />
    <Seekbar.Progress />
    <Seekbar.Thumb />
  </Seekbar.Track>
</Seekbar.Root>

API reference

Parts accept the attributes of the element they render, plus a ref, except where noted. Every part that renders an element also sets the media state attributes. Passing aria-label replaces a default label.

Seekbar.Root

renders <div role="slider">

The interactive timeline. Click or drag to seek. If the media was playing, it pauses during a drag and resumes on release.

It can take keyboard focus and keeps aria-valuenow, aria-valuemax and aria-valuetext up to date. Its default aria-label is "Video player slider". It is always position: relative; touch-action: none.

PropTypeDefaultDescription
skipIntervalnumber5Seconds to skip per arrow key press. At least 1. Fractions are rounded down, and anything else throws a ReactMediaKitError.
Data attributeDescription
data-draggingA pointer is pressed on the seekbar, for a click or a drag. Removed on release.
CSS variableDescription
--progress-percentHow much has played, as a plain number from 0 to 100, such as 42.50. Use calc(var(--progress-percent) * 1%) to turn it into a percentage length. The parts inside use it.
--buffer-percentHow much has buffered, in the same format. It stays unset until the first buffering or seek update after the seekbar mounts, so read it with a fallback: var(--buffer-percent, 0). When nothing is buffered, it matches the current position.

Your onKeyDown, onPointerDown, onPointerMove and onLostPointerCapture handlers run before the built-in ones; call event.preventDefault() in one to skip its built-in behavior.

Seekbar.Track

renders <div>

The rail. It holds Seekbar.Buffer, Seekbar.Progress and Seekbar.Thumb. It is position: relative and isolation: isolate, so the thumb’s high z-index doesn’t cover other content on the page. Give it a size, but don’t change its position.

Seekbar.Buffer

renders <div>

The buffered range, inside Seekbar.Track. Its width follows --buffer-percent, and it sits below Seekbar.Progress. It is position: absolute; height: 100% with an inline width, so don’t set those in CSS.

Seekbar.Progress

renders <div>

The played range, inside Seekbar.Track. Its width follows --progress-percent, and it sits above Seekbar.Buffer. It is position: absolute; height: 100% with an inline width, so don’t set those in CSS.

Seekbar.Thumb

renders <div>

The handle at the current position. It sits inside a full-size wrapper that moves it along the track with --progress-percent. className, ref and other <div> attributes are applied to the thumb itself.

The thumb is centered with an inline transform, so a transform in your CSS has no effect. Use scale, translate or rotate instead. The wrapper ignores pointer events, and the thumb inherits that.

CSS variableDescription
--seekbar-thumb-z-indexSets the z-index of the wrapper around the thumb. Defaults to 9999.

Keyboard interactions

Active while the seekbar has focus.

KeyAction
Left arrowDown arrowSkip back by skipInterval seconds.
Right arrowUp arrowSkip forward by skipInterval seconds.
HomeSeek to the start.
EndSeek to the end.

Media state attributes

Five of these describe loading, and exactly one of them is set at a time. The other three mark features the browser lacks. Use them to hide controls that can’t work, or to style the player while it loads.

Data attributeDescription
data-media-pendingLoading hasn’t started. This is the initial state.
data-media-loadingThe media has started loading.
data-media-metadata-loadedThe duration and dimensions are known, but there isn’t enough data to play yet.
data-media-playableThe media can play. It stays set if playback later stalls to buffer.
data-media-errorThe media element reported an error, such as a network or decoding failure. A blocked play() call doesn’t set it; use onError for that.
data-fullscreen-unsupportedThe browser can’t go fullscreen, or support hasn’t been detected yet.
data-pip-unsupportedPicture-in-picture isn’t available, such as for audio, or support hasn’t been detected yet.
data-volume-change-unsupportedThe browser doesn’t let scripts set the volume, such as on iOS, or support hasn’t been detected yet.