Timeline Playhead
A draggable timeline playhead: a vertical line with a top knob, positioned by time (currentTime × pixelsPerSecond × zoom, the same basis as timeline-ruler) and scrubbable. Drag it to seek; it scrolls with the tracks it sits over. Spans whatever container it is placed in. The playback clock and click-to-seek on the tracks are left to the consumer.
The playhead — the vertical cursor that marks the current time on an editor's
timeline. It positions itself by time (currentTime × pixelsPerSecond ×
zoom, the same basis as timeline-ruler) and fills whatever container
it sits in, so it spans the ruler and every track beneath it. Drag the line or
its knob to scrub; because it lives inside the scrolled content, it scrolls in
lockstep with the tracks.
It is controlled: render it at currentTime and update from onSeek. The
handles are focusable, so arrow keys nudge frame by frame.
It is only the cursor. The playback clock and click-to-seek on the ruler or
tracks are application concerns — the consumer wires the container's click to
onSeek and feeds the playback time back in. The component draws a draggable
line, nothing more.
Installation
One-time setup: add the @ikui registry to your components.json.
{
"registries": {
"@ikui": "https://ik-ui.pages.dev/r/{name}.json"
}
}Then install the component:
pnpm dlx shadcn@latest add @ikui/timeline-playheadUsage
import { TimelinePlayhead } from "@/components/timeline-playhead";const [time, setTime] = useState(0)
<div style={{ position: "relative", width: duration * 50 }}>
<TimelineRuler duration={duration} />
{/* …tracks… */}
<TimelinePlayhead
currentTime={time}
duration={duration}
onSeek={setTime}
/>
</div>The playhead is absolutely positioned and measures pointer scrubs against its
parent, so place it inside the time-scaled content — a position: relative
container of width duration × pixelsPerSecond × zoom that also holds the ruler
and tracks. It then spans them all and stays aligned to the ticks.
Examples
Across the tracks
The playhead sitting over a timeline-ruler and an audio clip
(timeline-element + audio-waveform), spanning both. Click anywhere to move
it, or drag the knob to scrub — the consumer turns the container's click into
onSeek; the playhead handles its own drag.
Props
| Prop | Type | Default |
|---|---|---|
currentTime | number | - |
duration | number | - |
pixelsPerSecond | number | 50 |
zoom | number | 1 |
step | number | 0.1 |
color | string | "currentColor" |
onSeek | (time: number) => void | - |
className | string | - |
style | React.CSSProperties | - |