Track Timeline
Arrange segments on tracks along a time ruler, and move, trim and snap them by pointer or keyboard, such as video clips.
import { TimelineClip, TimelineTrack, TrackTimeline } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use a track timeline for an edit made of parts on tracks: a podcast cut, a song sketch, a video's voice-over, a meeting's highlights. Each track is a lane in its chart color with its name, mute and solo keys and a level strip; each clip is a rounded bar holding the slice of the track's sound it plays, drawn from loudness samples you pass (it decodes no audio) and scaled by its gain. Drag a clip to move it, or its edges to trim it. Both snap to the other clips' edges and to the playhead, and a guide shows the edge they caught. A ruler of mono timecodes runs along the top, with the playhead on it. For a single recording's seek bar, use a Waveform Scrubber.
import { TrackTimeline } from '@youj/react'
<TrackTimeline
tracks={[
{ id: 'host', name: 'Host', caption: 'Mic 1', peaks: hostPeaks },
{ id: 'music', name: 'Music bed', peaks: musicPeaks, muted: true },
]}
clips={clips}
onClipsChange={setClips}
onTrackChange={(id, change) => updateTrack(id, change)}
duration={30}
position={time}
onPositionChange={setTime}
value={selected}
onValueChange={setSelected}
/>clips is controlled: every move and trim reports the whole list through onClipsChange. A clip sits from start to end on its track, plays its track's source from offset, and scales its wave by gain in decibels. A track's peaks are its whole source as loudness from 0 to 1, one sample every resolution seconds (0.1). A clip never moves into its neighbor on the same track, never trims past its source, and stays at least half a second long. formatClipTime(seconds, locale) formats times the way the timeline speaks them (0:12.5).
Description
A podcast edit on three tracks: Host, Guest and Music bed.
Moves and trims snap within 8px of another clip's edge, the playhead or either end; hold Alt to place freely, or pass snap={false}. The timeline opens with the whole of it in view (at least 8px a second); − and + (or Ctrl or ⌘ and the wheel) zoom about the playhead, and zoom controls it in pixels per second. Pass playing while your player runs: the level strips read each track at the playhead, and the lane turns the page as the playhead nears its end. The Song Sketch block puts it together with a transport, an inspector and simulated playback.
Code
track-timeline-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propstracks, clips, duration… (21)
Props
| Prop | Type | |
|---|---|---|
tracksRequired | readonly TimelineTrack[] | – |
clipsRequired | readonly TimelineClip[]The clips (controlled): every move and trim reports the whole list. | – |
onClipsChange | (clips: TimelineClip[]) => void | – |
onTrackChange | (id: string, change: { muted?: boolean; solo?: boolean }) => void | – |
durationRequired | numberThe timeline's length, in seconds. | – |
position | numberThe playhead, in seconds (controlled). | – |
defaultPosition | numberdefault 0 | 0 |
onPositionChange | (seconds: number) => void | – |
value | string | nullThe selected clip's id (controlled); | – |
defaultValue | string | nulldefault null | null |
onValueChange | (id: string | null) => void | – |
zoom | numberPixels per second (controlled). Default: the whole timeline in view, at least 8. | – |
defaultZoom | number | – |
onZoomChange | (pxPerSecond: number) => void | – |
resolution | numberSeconds per sample in the tracks' peaks. default 0.1 | 0.1 |
snap | booleanSnap moves and trims to the other clips' edges and the playhead. default true | true |
step | numberSeconds an arrow key moves or trims a clip. default 0.5 | 0.5 |
playing | booleanWhile playing, the meters read the tracks at the playhead and the lane follows it. default false | false |
label | stringThe timeline's accessible name. Default: the labels' "Timeline". | – |
locale | stringDefault: the YoujProvider locale. | – |
sound | booleanWith sound on, catching a snap point plays default true | true |
Other props pass through: extends ComponentProps<'div'>. | ||
StylingParts and data attributes
Styling
Parts: track-timeline (with data-dragging), track-timeline-heads, track-timeline-zoom, track-timeline-track-head (with data-muted and data-solo), track-timeline-track-name, track-timeline-track-caption, track-timeline-mute, track-timeline-solo, track-timeline-meter, track-timeline-lane, track-timeline-ruler, track-timeline-ruler-control, track-timeline-ruler-track, track-timeline-time, track-timeline-playhead, track-timeline-tracks, track-timeline-track, track-timeline-clip (with data-selected and data-muted), track-timeline-clip-name, track-timeline-wave, track-timeline-handle (with data-edge), track-timeline-playhead-line and track-timeline-snap. In Bezel the track captions are mono capital eyebrows and the timecodes and clip names are mono. On a narrow timeline the heads keep the name, the keys and the level strip.
MotionPlaying, drag and trim, handles · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Playingeach level strip eases between readings as the playhead passes through the track’s clips | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) | |
| Drag and trimthe clip and its edges follow the pointer, never eased; a snap shows its guide at once | no token | ||
| Handlesa trim handle thickens on hover | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) |
Reduced motion
The level strips jump to each reading; everything else was never eased.
Reduced motion
The level strips jump to each reading instead of easing. Dragging, trimming and the playhead are unchanged, since they never animate.
AccessibilityKeyboard, ARIA, focus
Accessibility
- The timeline is a group named by
label(default "Timeline"), and each track a group named by the track. Every clip is a button and a tab stop, named by its name, range and track ("answer, 0:06.5 to 0:12.5, Guest") and described by a hint that explains the keys. - Focusing a clip selects it (
aria-pressed). Arrow keys move it bystep(half a second), Shift ten times as far, stopping at the first snap point on the way; the new range is announced. Escape lets go of it. - The selected clip's edges come next in the tab order, as sliders named "Start of answer" and "End of answer" that trim it from the keyboard.
- The playhead is a slider on the ruler, spoken as the time and the length ("0:09.0 of 0:30.0"). Mute and solo are toggle buttons named with their track ("Mute Guest").
- Time runs left to right in every script, like a tape, so the lane keeps its direction under RTL, the arrow keys included; the heads and their text follow the page.
- With sound on, catching a snap point while dragging plays
tick. - Its words come from the labels (
trackTimeline,trackTimelineHint,trackTimelineClip,trackTimelineRange,trackTimelineClipStart,trackTimelineClipEnd,trackTimelineMute,trackTimelineSolo,trackTimelinePlayhead,zoomIn,zoomOut), translated by the language packs.