TimeRuler

5 parts

A headless, zoomable time axis: a horizontal ruler of ticks and labels over a span of duration seconds. It renders the accessible group (region when labelled), measures its own width, and builds a useScale whose domain is the visible time window [offset, offset + width / zoom] and range [0, width]. The visible window is driven by two models — offset (the left-edge time in seconds, v-model:offset) and zoom (pixels-per-second, v-model:zoom) — which stream continuously while panning / zooming; the root additionally emits SETTLE events (panCommit, zoomCommit, rangeChange) when a gesture ends. Tick generation is selected by mode: 'seconds' uses the human time ladder, 'timecode' renders HH:MM:SS:FF SMPTE labels at fps (drop-frame optional), and 'frames' renders integer frame numbers. When focusable the root handles a keyboard layer (Arrow keys pan, Shift+Arrow pans by a major interval, + / - zoom about the canvas centre) and optional wheel / drag panning. It is usable standalone or embedded in a Timeline via TimeRulerContext, which exposes the tick collections, the scale / invert projectors, the offset / zoom models, and the formatTime helper. The default slot surfaces { ticks, majorTicks, minorTicks, scale, formatTime } so consumers can render their own tick layer; the TimeRuler* parts are opt-in.

Demo

Loading demo…

Anatomy

Import the parts and compose them. Each part forwards attributes to its underlying element.

vue
import {
  TimeRulerRoot,
  TimeRulerTick,
  TimeRulerLabel,
  TimeRulerCursor,
  TimeRulerScreenReaderSummary,
} from '@robonen/primitives/canvas/time-ruler';

<TimeRulerRoot>
  <TimeRulerTick />
  <TimeRulerLabel />
  <TimeRulerCursor />
  <TimeRulerScreenReaderSummary />
</TimeRulerRoot>

API Reference

TimeRulerRoot

Root

A headless, zoomable time axis: a horizontal ruler of ticks and labels over a span of `duration` seconds. It renders the accessible `group` (region when labelled), measures its own width, and builds a `useScale` whose domain is the visible time window `[offset, offset + width / zoom]` and range `[0, width]`. The visible window is driven by two models — `offset` (the left-edge time in seconds, `v-model:offset`) and `zoom` (pixels-per-second, `v-model:zoom`) — which stream continuously while panning / zooming; the root additionally emits SETTLE events (`panCommit`, `zoomCommit`, `rangeChange`) when a gesture ends. Tick generation is selected by `mode`: `'seconds'` uses the human time ladder, `'timecode'` renders `HH:MM:SS:FF` SMPTE labels at `fps` (drop-frame optional), and `'frames'` renders integer frame numbers. When focusable the root handles a keyboard layer (Arrow keys pan, Shift+Arrow pans by a major interval, `+` / `-` zoom about the canvas centre) and optional wheel / drag panning. It is usable standalone or embedded in a `Timeline` via `TimeRulerContext`, which exposes the tick collections, the `scale` / `invert` projectors, the `offset` / `zoom` models, and the `formatTime` helper. The default slot surfaces `{ ticks, majorTicks, minorTicks, scale, formatTime }` so consumers can render their own tick layer; the `TimeRuler*` parts are opt-in.

PropTypeDescription
duration?numberTotal content duration in seconds.
fps?numberFrame rate used by 'timecode' / 'frames' modes.
mode?TimeRulerModeHow tick labels are rendered.
dropFrame?booleanUse drop-frame timecode labels (only meaningful in 'timecode' mode).
minZoom?numberMinimum zoom in pixels-per-second; zoom is clamped to this.
maxZoom?numberMaximum zoom in pixels-per-second; zoom is clamped to this.
disabled?booleanDisable all interaction (keyboard, wheel, drag).
dir?TimeRulerDirectionWriting direction. When omitted it is inherited from the nearest ConfigProvider (falling back to 'ltr'); an explicit value wins.
targetDensity?numberTarget pixel spacing between adjacent ticks, forwarded to the tick generator.
focusable?booleanMake the ruler focusable and enable the keyboard layer.
wheel?booleanEnable wheel panning (and ctrl/cmd+wheel zoom about the pointer).
draggable?booleanEnable drag-to-pan (x-only).
offset?numberTwo-way bound value (v-model:offset).
zoom?numberTwo-way bound value (v-model:zoom).
Emits
EventPayload
update:offset[value: number]
update:zoom[value: number]

TimeRulerTick

Tick

A single gridline of the ruler. Consumers usually render their own tick layer by looping the `ticks` exposed on `TimeRulerRoot`'s default slot, but this part provides a ready-made, positioned element for the common case. It is `aria-hidden` by default (the screen-reader summary announces the window instead of every tick), reflects `tick.major` on `data-major`, and positions itself at `tick.px` via an inline `left` (overridable through merged styles).

PropTypeDescription
tickTickThe tick to render (from TimeRulerRoot's exposed ticks).

TimeRulerLabel

Label

The text label for a tick (`tick.label`). Rendered for `major` ticks in the default tickers; `aria-hidden` so the visual labels do not duplicate the screen-reader summary. Positioned at `tick.px` like `TimeRulerTick`; the default slot falls back to `tick.label` so it works without a slot.

PropTypeDescription
tickTickThe tick whose label to render.

TimeRulerCursor

Cursor

An optional playhead / hover indicator, positioned at a given `time` (seconds) projected through the ruler's `scale`. Purely presentational (`role` defaults to `'presentation'` and it is `aria-hidden`) — it conveys no semantics of its own; the playhead time should be announced by the host (e.g. a transport control), not the ruler. When the projected pixel falls outside the visible width the cursor is hidden (rendered with `visibility: hidden`) so an off-screen playhead does not bleed past the ruler edges; the element stays in the tree for stable transitions.

PropTypeDescription
timenumberPlayhead / hover time in seconds.

TimeRulerScreenReaderSummary

ScreenReaderSummary

A visually-hidden, `aria-live="polite"` region that announces the visible window after it settles — the accessible alternative to announcing every tick. It debounces on every `offset` / `zoom` / `mode` / `fps` change and, once the viewport stops moving for `settleDelay` ms, writes a single human summary: the visible time range, the seconds-per-tick (zoom granularity), and the frame rate (in frame-based modes). Provide `formatMessage` to fully customise the announcement.

PropTypeDescription
settleDelay?numberDebounce before announcing, in milliseconds.
formatMessage?(view: { start: number; end: number; secondsPerTick: number; zoom: number; fps: number; mode: string; formatTime: (seconds: number) => string; }) => stringBuild the announcement string. Receives the settled view (visible start / end seconds, secondsPerTick, zoom, fps, mode) and the bound formatTime helper. Defaults to a "Showing <a> to <b>, …" sentence.