Mischief

108 / Controls

Scrub Bar

The seek control on its own: a slider that happens to be a timeline, announced in minutes and seconds.

0:42
3:34

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/scrub-bar
import { ScrubBar } from "mischief-ui/scrub-bar"

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/scrub-bar/scrub-bar.tsx
"use client" import * as React from "react" import { cn } from "@/lib/utils" export type ScrubBarProps = Omit<  React.HTMLAttributes<HTMLDivElement>,  "onChange" | "defaultValue"> & {  /** Seconds. */  duration: number  value?: number  defaultValue?: number

Usage

export function Player() {
  return (
    <ScrubBar
      duration={214}
      value={at}
      buffered={158}
      onValueChange={setAt}
      onCommit={(seconds) => player.seek(seconds)}
    />
  )
}

Dragging and arriving

onValueChange fires all the way through a drag, so the time and the bar keep up with the pointer. onCommit fires once, when the drag ends. Seeking a real player on every change of a drag is how a scrub turns into a stutter.

What has arrived

buffered draws a second, quieter fill behind the played part, so the difference between somewhere you can go and somewhere still downloading is visible before it is clicked.

API

durationnumberLength in seconds.
valuenumberPosition in seconds when controlled.
defaultValuenumberPosition when it is not.
onValueChange(seconds: number) => voidThroughout a drag.
onCommit(seconds: number) => voidOnce, when the drag ends.
stepnumberSeconds an arrow key moves. Defaults to 5.
bufferednumberHow much has downloaded, in seconds.
labelstringNames the slider. Defaults to "Seek".
disabledbooleanTurns it off and takes it out of the tab order.

Accessibility

It is a real slider: role, minimum, maximum and current value, focusable, with arrow keys moving by step and Home and End going to either end. aria-valuetext reads it as a time -- one minute forty of three minutes thirty-four -- because a screen reader announcing the raw second count of a recording tells a listener nothing they can use. The handle appears on hover and on focus, so it is never a pointer-only affordance.