Documentation

Canvas renderer customization

Customize canvas drawing, override themes, specify metrics, and draw custom layers.

Browse documentation

Local theme overrides

Use the theme prop when canvas-painted visuals need explicit overrides:

import { CanvasRenderer } from '@techsquidtv/canvas-timeline';
<CanvasRenderer
theme={{
colors: {
clip: {
bg: '#252525',
bgSelected: '#303030',
borderSelected: '#eab308',
textSelected: '#18181b',
},
marker: {
fill: '#a1a1aa',
text: '#a1a1aa',
},
},
}}
/>;

The renderer merges partial overrides with built-in canvas defaults. Explicit theme values win over documented --timeline-* variables, which win over documented shadcn semantic variables such as --background, --foreground, --accent, --primary, --ring, and --font-mono, which win over renderer defaults. Undocumented timeline aliases are intentionally ignored.

Track row height comes from timeline data (track.height). Renderer metrics only shape the built-in clip body inside that row:

<CanvasRenderer
theme={{
metrics: {
clipRadius: 6,
clipInsetY: 4,
clipLabelPaddingX: 12,
},
}}
/>

Keyframe drawing

CanvasRenderer draws clip keyframe segments and diamonds when you pass a registered keyframeProperty id. The main-thread renderer binding asks the engine for prepared keyframe geometry, then the worker draws that serializable geometry. The worker does not normalize property values or assume opacity.

Disable the built-in keyframe layer when an app wants to draw custom automation curves:

<CanvasRenderer showKeyframes={false} />

Use Timeline.KeyframeInteractionLayer for DOM hit targets over canvas-drawn keyframes. Use Timeline.KeyframeTangentInteractionLayer for optional inline Bezier handles on selected segments. Fully custom renderers can read keyframe geometry through useTimelineKeyframes() and segment/tangent geometry through useTimelineKeyframeSegments().

Theme changes

When your application changes theme classes or root CSS variables, update themeKey so CanvasRenderer re-reads those variables and posts updated options to the worker:

import { CanvasRenderer, Timeline } from '@techsquidtv/canvas-timeline';
<section className={themeName === 'dark' ? 'dark editor-theme' : 'editor-theme'}>
<Timeline.Root>
<CanvasRenderer themeKey={themeName} />
<Timeline.PlayheadArea />
<Timeline.PlayheadGrabber />
</Timeline.Root>
</section>;

Use a stable primitive such as 'light', 'dark', a brand id, or a numeric revision. Do not change themeKey on every frame.

Ruler formats and spacing

CanvasRenderer measures ruler labels against the resolved ruler font and automatically increases major-tick spacing when needed. This keeps frame-aware timecodes legible when an app changes --timeline-font-ruler or supplies a renderer font override.

Use the explicit format discriminant to select elapsed seconds, production timecode, or absolute project frame numbers. Frame-aware formats require the project frame rate:

<CanvasRenderer ruler={{ format: 'seconds' }} />
<CanvasRenderer ruler={{ format: 'timecode', frameRate: 30 }} />
<CanvasRenderer ruler={{ format: 'frame-number', frameRate: 30 }} />

Frame-aware rulers derive their major intervals from the project rate, then choose medium and minor intervals that evenly divide every major interval. This keeps editorial boundaries and visual cadence consistent across the ruler.

Use minimumMajorTickSpacing when the composition needs additional breathing room beyond the measured label width:

<CanvasRenderer
ruler={{
format: 'timecode',
frameRate: 30,
minimumMajorTickSpacing: 96,
}}
/>

The value is a minimum in CSS pixels; it cannot reduce the library’s format-safe spacing.

Clip colors

Clip fill color is canvas-painted. The renderer uses this order:

  1. clip.color from timeline data.
  2. The selected or default clip colors from the resolved renderer theme.
  3. Built-in renderer defaults.

Treat clip.color as project data, not component styling. It is useful for media categories, track roles, labels, or imported project metadata. When a clip does not define clip.color, the package theme derives default and selected clip fills from the host app’s panel, accent, and foreground tokens. For general app theme changes, prefer CSS variables plus CanvasRenderer themeKey.

Markers follow the same data-versus-theme boundary. marker.color wins for the marker pin fill when it is present in timeline data. Otherwise, the pin fill uses --timeline-marker (falling back to --muted-foreground and its built-in neutral). The marker label text color is independently controlled by --timeline-marker-text (falling back to --timeline-ruler-text, --muted-foreground, and built-in neutral), and is not affected by marker.color.

Custom dense visuals

Use TimelineCanvasLayer for thumbnail strips, waveforms, annotations, and other dense visuals that should not become per-clip DOM. The layer gives your app visible clip geometry and source-time ranges, but your app owns media decoding and caches. Do not fetch, decode, or generate images inside draw(); draw cached ImageBitmap, HTMLImageElement, video-frame canvas, or other CanvasImageSource values and call requestDraw() when an async cache finishes.

import { CanvasRenderer, TimelineCanvasLayer } from '@techsquidtv/canvas-timeline';
const thumbnailCache = new Map<string, CanvasImageSource>();
// inside your timeline root:
<CanvasRenderer showClips={false} />
<TimelineCanvasLayer
overscanPixels={128}
draw={({ ctx, visibleClips, requestDraw }) => {
for (const clip of visibleClips) {
const image = thumbnailCache.get(clip.clip.sourceId);
if (!image) {
loadThumbnail(clip.clip.sourceId).then((nextImage) => {
thumbnailCache.set(clip.clip.sourceId, nextImage);
requestDraw();
});
continue;
}
ctx.drawImage(
image,
clip.visibleRect.x,
clip.visibleRect.y,
clip.visibleRect.width,
clip.visibleRect.height
);
}
}}
/>

For custom DOM clip renderers, use useTimelineVisibleClips() and render only the returned visible entries. DOM composition is flexible, but large thumbnail or waveform timelines should prefer a canvas layer to avoid layout work.

Performance rules

  • Keep repeated visuals on canvas: clips, tracks, rulers, markers, snap lines, in/out shading, waveforms, thumbnails, and keyframes.
  • Style low-count DOM affordances with CSS: scrollbars, grabbers, the active clip interaction layer, focus rings, and editor chrome.
  • Let clip drag and trim interactions use pointer capture on the delegated interaction layer; avoid window-level drag listeners that duplicate pointer move handling.
  • Resolve CSS variables only when the theme changes, not during scrolling, scrubbing, zooming, playback, or rendering frames.