Timeline

Compound timeline for keyframe animation editing with playhead, tracks, and transport controls.

Install

npx shadcn@latest add https://substrate.georgedrury.co.uk/r/timeline

This also installs the animation-store dependency (Zustand).

Examples

0:00
0:01
0:02
0:03
0:04
0:05
Position
Opacity
Scale
0:00.00 / 0:05.00
"use client"

import {

Theme variables

VariableTailwind classFallbackDescription
--timeline-playheadbg-timeline-playhead--color-destructivePlayhead colour
--timeline-keyframebg-timeline-keyframe--color-muted-foregroundKeyframe diamond colour
--timeline-trackbg-timeline-track--color-panelTrack background

Components

ComponentDescription
TimelineFlex-column container with panel background and top border
TimelineRulerSecond-interval tick marks with time labels. Fixed label column at 128px width
TimelinePlayheadDraggable vertical line with triangular handle. Updates animation-store on drag
TimelineTrackSingle track row with a label column and a content area for keyframes
TimelineKeyframeDiamond-shaped marker positioned by time * zoomLevel pixels
TimelineTransportPlay/pause, loop, and time display. Reads directly from animation-store

Props

Timeline

PropTypeDefaultDescription
durationnumber5Total duration (unused internally — pass to children)

TimelineRuler

PropTypeDefaultDescription
durationnumber—Total duration in seconds
zoomLevelnumber—Pixels per second

TimelinePlayhead

PropTypeDefaultDescription
timenumber—Current time in seconds
durationnumber—Total duration in seconds
zoomLevelnumber—Pixels per second

TimelineKeyframe

PropTypeDefaultDescription
timenumber—Keyframe time in seconds
durationnumber—Total duration in seconds
zoomLevelnumber—Pixels per second
selectedbooleanfalseWhether keyframe is selected
onSelect() => void—Click handler for selection

TimelineTransport

No additional props. Reads state from useAnimationStore.

Animation store

The timeline uses a shared Zustand store (animation-store) for playback state:

import { useAnimationStore } from "@/stores/substrate/animation-store"

const { currentTime, isPlaying, togglePlayback } = useAnimationStore()
PropertyTypeDescription
durationnumberTotal duration (default 5s)
currentTimenumberCurrent playhead time
isPlayingbooleanPlayback state
fpsnumberFrames per second (60)
zoomLevelnumberPixels per second (100)
selectedKeyframeIdsSet<string>Selected keyframe IDs
setCurrentTime(time: number) => voidSet time (clamped)
togglePlayback() => voidToggle play/pause
setZoomLevel(level: number) => voidSet zoom (clamped 20–500)
selectKeyframe(id: string, additive?: boolean) => voidSelect keyframe
clearKeyframeSelection() => voidClear selection