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 curves and diamonds by default. The renderer
uses the same keyframe interpolation helpers as the engine, so canvas visuals
match evaluated linear, hold, and bezier property values.
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.KeyframeCurveInteractionLayer for optional inline
Bezier handles on selected curve segments. Fully custom renderers can read
keyframe geometry through useTimelineKeyframes() and curve geometry through
useTimelineKeyframeCurves().
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.
Clip colors
Clip fill color is canvas-painted. The renderer uses this order:
clip.colorfrom timeline data.- The selected or default clip colors from the resolved renderer theme.
- 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.