daw-ui

Timeline

Shows time, a ruler, a grid and one playhead, over whatever it holds, and moves without rendering.

Zoom

Anatomy

import { musicalGrid } from '@addstack/daw-ui';
import { Timeline } from '@addstack/daw-ui/react';

const grid = musicalGrid({ bpm: 120 });

<Timeline.Root start={0} end={30} read={() => transport.time}>
  <Timeline.Ruler grid={grid}>
    <Timeline.Grid grid={grid} />
  </Timeline.Ruler>
  <div className="relative">
    <Timeline.Grid grid={grid} />
    {/* Whatever you place on the axis: rows of regions, notes, a waveform. */}
  </div>
  <Timeline.Playhead />
</Timeline.Root>

Timeline.Root is the time axis: start … end seconds fill its width. The ruler labels it, the grid draws its lines, and the playhead runs across its whole height. That is all a timeline does: it shows time.

Everything on the axis places itself in CSS from two variables the root writes:

CSS variable
--timeline-startSeconds at the left edge.
--timeline-scaleCSS pixels per second.

Scrolling or zooming writes these two, whatever the timeline holds: nothing renders, and nothing runs for each thing on it. The playhead is written, as a number, only into the parts that follow it (Playhead, Waveform.Progress, Notes.Progress), so a frame of playback recalculates the style of those parts alone. To follow the playhead with an element of your own, render it as the Playhead (render={<YourLine />}).

Time runs left to right in every language, as in music notation and every DAW, also in a right-to-left page.

What it holds

A timeline works on its own, as in the demo: time, a ruler in bars and beats, another in minutes and seconds, the grid, the playhead. What else it holds, it does not know: an arrangement's tracks, a piano roll's notes, one take, or nothing. Parts placed inside read its axis, and it knows nothing of them:

  • a Region takes its place in time from the timeline;
  • a Waveform or Notes in a region show what the region shows.

The Arrangement block composes them into tracks of clips, with their headers in a column beside the timeline.

Playback, scrolling and zoom

Pass values that change on every frame as functions, which the timeline calls once per animation frame:

<Timeline.Root
  start={0}
  end={30}
  read={() => transport.time}
  readView={() => [view.start, view.end]}
/>

start, end and position are for values that change rarely, from React state. The view in the demo zooms with a fader whose onValueChange writes to a ref that readView reads: dragging it renders nothing.

The timeline does not scroll or zoom by itself: it shows the view you give it, so the gestures and their policy (scroll with the wheel, zoom around the pointer, follow the playhead) stay in your application.

Ruler and grid

A grid says where lines fall and what they read. musicalGrid({ bpm, beatsPerBar, divisions }) gives sixteenths, beats, bars and groups of bars at a constant tempo, read as a song position; clockGrid() gives milliseconds to hours, read as 1:05.25. Both print digits in the user's locale. Make a grid once, outside the component or with useMemo.

Timeline.Ruler labels the grid; Timeline.Grid draws its lines. Each uses the finest step of the grid that leaves spacing pixels between lines at the zoom, so zooming out goes from sixteenths to beats, to bars, to groups of bars:

<Timeline.Ruler grid={grid} className="h-6 text-xs [&_[data-label]]:ps-1">
  {/* Marks under the labels: a grid in the ruler, from 65% of its height down. */}
  <Timeline.Grid grid={grid} style={{ top: '65%' }} className="text-neutral-600" />
</Timeline.Ruler>

<div className="relative">
  {/* Minor and major lines under what the timeline holds: two grids, the major ones as far apart as the labels. */}
  <Timeline.Grid grid={grid} className="text-white/5" />
  <Timeline.Grid grid={grid} spacing={64} className="text-white/15" />
  {tracks}
</div>

Lines are drawn like waveforms, in canvas tiles in the element's CSS color, and labels are text placed in time: scrolling and playback draw and render nothing, and the labels change only when the view moves half its width or the zoom changes the step. Labels are spans with data-label: style them in CSS.

Your own grid is an object with steps, seconds between lines from fine to coarse, each a whole multiple of the one before, and label(time, step), for a tempo map or frames of video.

API reference

Root

The time axis. Renders a <div> with position: relative.

Prop

Type

Playhead

A line at the playhead, across the timeline's height. Renders a <div>, hidden from assistive technology: give it a width and a color.

Prop

Type

Ruler

The labels of a grid: a <div> with position: relative, holding a <span data-label> at every line of the grid spacing pixels apart, and its children. Hidden from assistive technology.

Prop

Type

Grid

Lines at every step of a grid spacing pixels apart: a <div> that fills its container, with canvas tiles, in its CSS color. Hidden from assistive technology.

Prop

Type

On this page