108 / Controls
Scrub Bar
The seek control on its own: a slider that happens to be a timeline, announced in minutes and seconds.
"use client"
import * as React from "react"
import { ScrubBar } from "mischief-ui/scrub-bar"
export function ScrubBarDemo() {
const [at, setAt] = React.useState(42)
return (
<div className="w-full max-w-md">
<ScrubBar
duration={214}
value={at}
buffered={158}
onValueChange={setAt}
label="Seek the recording"
/>
</div>
)
}Installation
Copy the source into your project, or keep it behind a package.
npx shadcn@latest add Tinkerers-Labs/mischief-ui/scrub-barimport { 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.
"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?: numberUsage
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.