Timeline Element
A positioned, trimmable timeline clip: places itself on a track by time (startTime/duration × pixelsPerSecond × zoom, the same basis as timeline-ruler) and renders any content inside — an audio waveform, a thumbnail strip, a label. Selectable, with left/right trim handles that emit the new geometry. The track, playhead, and seeking are left to the consumer.
A timeline clip — the block that sits on an editor's track. It positions
itself by time (startTime / duration × pixelsPerSecond × zoom, the
same basis as timeline-ruler), so dropping it into a track lines it up
under the ruler. The clip is content-agnostic: put an audio-waveform, a
thumbnail-strip, a label — anything — inside as children.
When selected it draws a border and left/right trim handles; dragging a
handle resizes the clip and reports the new { startTime, duration } through
onResize / onResizeEnd. Trimming is clamped to startTime >= 0 and
minDuration.
It is only the clip. The track lane, the playhead, seeking, snapping, and mapping a trim back to a media source offset are application concerns — the consumer composes them. The element renders a positioned, trimmable block, 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-elementUsage
import { TimelineElement } from "@/components/timeline-element";const [clip, setClip] = useState({ startTime: 0, duration: 10 })
<div style={{ position: "relative", width, height: 56 }}>
<TimelineElement
startTime={clip.startTime}
duration={clip.duration}
pixelsPerSecond={50}
selected
onResize={setClip}
>
<AudioWaveform audioUrl="/clip.mp3" height={56} />
</TimelineElement>
</div>The element is controlled: it renders at the given startTime / duration
and emits the new geometry while trimming — store it and pass it back. It is
absolutely positioned, so it expects a position: relative track of the right
width (totalDuration × pixelsPerSecond × zoom).
To keep the content aligned to the timeline (instead of stretching with the
clip), render it at the full source width and offset it by
-startTime × pixelsPerSecond; the clip's overflow: hidden windows it.
Examples
Combined with the ruler
timeline-ruler supplies the scale; a video clip (thumbnail-strip) and an
audio clip (audio-waveform) sit on their tracks beneath it, all sharing one
width and pixelsPerSecond. Click a clip to select it, then drag its handles
to trim — the cuts stay aligned to the ruler's ticks. This is the core of a
video/audio editor's timeline, built from primitives.
A segmented track
segmented-timeline-strip bakes in its own proportional scale, playhead, and
seek. The composable equivalent is a row of TimelineElements on one lane —
here one video split into segments, each clip windowing the same
thumbnail-strip — under a shared timeline-ruler and a single
timeline-playhead. Click a segment to activate it (others dim) and to move the
playhead. Same behavior as the strip, but in the timeline's pixelsPerSecond
coordinate system, so it scrolls, zooms, and aligns with everything else.
Props
| Prop | Type | Default |
|---|---|---|
startTime | number | - |
duration | number | - |
pixelsPerSecond | number | 50 |
zoom | number | 1 |
height | number | 56 |
color | string | "#915dbe" |
selected | boolean | false |
trimmable | boolean | true |
minDuration | number | 0 |
step | number | 0.1 |
onSelect | () => void | - |
onResize | (next: TimelineElementResize) => void | - |
onResizeEnd | (next: TimelineElementResize) => void | - |
children | React.ReactNode | - |
className | string | - |
style | React.CSSProperties | - |