Theme
Sound
Jump to a component
Core · Media
Timeline
Timeline is the cut's tracks under the preview, shared by every production (episode or short, Quick or Directed).
Live, from @anicanon/sakuga 2.0.0
Sakuga for iOS and Android is coming
iOS
Web only
Timeline is made for the web, and isn’t part of Sakuga for iOS yet.
Android
Web only
Timeline is made for the web, and isn’t part of Sakuga for Android yet.
- Use it for
- The cut's tracks under the preview: video, boards, overlays, sound, captions and effects in time, with the playhead and trim grips on the selected item.
- Not for
- The whole cut at a glance, in planned proportionProgramBar
- What gets generated for a cut on a frame gridTimesheet
- A manga page's layersLayerStrip
- The playhead of an episode a viewer watchesScrubber
Guidelines
durationand every item'sstartandlengthare in seconds. The ruler steps itself (1, 5 or 30 s) unlessstepis set;ratenames the frame rate shown at the ruler's head ("24 fps").- Tracks have a
kind:video(a picture strip),board(storyboard panels standing in for video),overlay(text and images),audio(waveform),caption(one language per track),effect. - Item
state:selected,gap(planned time with no picture yet; never hide it),board,rejected. - A
selecteditem shows a grip at each end: drag one to trim the item, so its time shrinks or grows from that end, a frame at a time and never over the item beside it;onTrimgets each new time. While trimming, passtrimmed({ start, end }in seconds) to draw the cut-off footage as a dashed ghost beyond the item. markersname points in time (a scene start, a beat). They have their own rail above the times: each is a flag that starts at its time, with a hairline from it down through the times to the tracks. A flag that would run past the end ends at its time instead, and one that would run into the next is cut short a gap before it, its full name in its title. No marker covers a time.playheaddraws the current time across every track.- Track names stay on one line in the 120 px head; a longer one is cut short, with its full name in its title.
- A
gaphas a dashed edge. With nolabel, it says what it is: "No picture yet", or "Storyboard" for aboarditem. - It never snaps item boundaries on its own: start and length are the author's. While you drag, an edge snaps to another item or the playhead and a line shows where.
reorderablegives every track head a drag handle: the order of titles and images decides what draws in front (the picture is always at the back). Drag a handle and its track lifts, a line shows where it lands, andonMoveTrack(from, to)is called when it is let go; the move is said then, not at each place it passes.moving({ track, to }, indexes) draws a track lifted, for a board.offset(seconds) labels the ruler in program time when the timeline shows a zoomed window of a longer cut.- Track kinds
dialogue,musicandaudio(sound) draw the coreWaveformat the item's width, in their own wave tokens; pass an item'speaksfor its real loudness. Tracks are told apart by their heads (icon and name), not by colour; the accent marks only the selection and the playhead.
onSelect(track, item)makes the items one Tab stop of buttons: Left and Right move within a track, Up and Down to the nearest item on the track above or below, Enter or Space selects.- A selected item's grips sit beside it in the lane and are sliders: an arrow trims by
trimStep(a frame, 1/24 s, by default), Shift, Page Up or Page Down by a second, and each move callsonTrim(track, item, edge, seconds). An edge stops at the item beside it on its track, never over it; Home and End take it that far. WithoutonTrimthe grips only draw. onMoveTrack(from, to): withreorderable, Up and Down on a track's handle move the track, focus stays on its handle, and the move is said ("Captions track moved to 2 of 5"). Enter or Space lifts the track instead: the arrows move where it lands, Enter or Space drops it there, and Escape puts it back.
import { Timeline } from '@anicanon/sakuga/ui';
const tracks = [
{
label: 'Video',
kind: 'video',
items: [
{ start: 0, length: 6, label: 'Take 2', state: 'selected' },
{ start: 6, length: 4, label: 'No picture yet', state: 'gap' },
],
},
{
label: 'Audio',
kind: 'audio',
items: [{ start: 0, length: 10, label: 'Rain on glass' }],
},
{
label: 'Captions JA',
kind: 'caption',
items: [{ start: 2, length: 1.6, label: '急いで!' }],
},
];
export default function TimelineExample() {
return (
<Timeline
duration={12}
playhead={4}
rate="24 fps"
markers={[{ at: 6, label: 'Scene 2' }]}
tracks={tracks}
/>
);
}PropsTimelineProps
durationrequirednumbertracksrequiredTimelineTrack[]playheadnumberoffsetnumberstepnumberratestringlabelstringonSelect(track: number, item: number) => voidMakes the items one Tab stop of buttons: arrows move between them, Enter or Space calls it with the track and item indexes.
onTrim(track: number, item: number, edge: 'in' | 'out', seconds: number) => voidA selected item's grip moved by keyboard or dragged: the new time of that edge, in seconds. An edge stops at the item beside it on the track, and a drag snaps to another item's edge or the playhead. Without it the grips only draw.
trimStepnumberWhat one arrow press trims, in seconds. Default 1/24 (a frame); Shift, Page Up and Page Down trim a second.
onMoveTrack(from: number, to: number) => voidWith reorderable, a track's handle moves it: Up and Down a place at once, Enter or Space to lift it and drop it further, or a drag.
markers{ at: number; label: string; }[]reorderablebooleanTrack heads carry a drag handle.
moving{ track: number; to: number; }Draws a track being moved, its index and where it lands (a board); a drag or the keys draw their own.
classNamestringTypes
interface TimelineTrack { label: string; kind: TimelineTrackKind; items: TimelineItem[]; }
Each token reads a role, and the role lands on a value in each theme. Change the role and every component that reads it follows.
Colour
timeline-bgTimeline: ground.bg-surfaceneutral-0#ffffffbg-surfaceneutral-900#111117timeline-borderTimeline: rules between rows and the ruler.border-hairlineshade-a08rgba(11,10,16,0.08)border-hairlinewhite-a06rgba(255,255,255,0.06)timeline-labelTimeline: track names and text items.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7timeline-rulerTimeline: time ticks.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7timeline-marker-bgTimeline: a named marker.accent-subtlecamellia-600-a10rgba(194,51,95,0.1)accent-subtlecamellia-400-a14rgba(244,119,159,0.14)timeline-marker-fgTimeline: marker label.text-accentcamellia-700#b42354text-accentcamellia-400#f4779ftimeline-item-bgTimeline: a video item without picture.bg-surface-raisedneutral-100#ececf0bg-surface-raisedneutral-875#1a1a21timeline-item-borderTimeline: item outline.border-dividershade-a12rgba(11,10,16,0.12)border-dividerwhite-a12rgba(255,255,255,0.12)timeline-audio-bgTimeline: an audio item.bg-fill-subtleshade-a06rgba(11,10,16,0.06)bg-fill-subtlewhite-a08rgba(255,255,255,0.08)timeline-waveTimeline: audio waveform bars.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7timeline-text-bgTimeline: caption and overlay items.bg-fill-subtleshade-a06rgba(11,10,16,0.06)bg-fill-subtlewhite-a08rgba(255,255,255,0.08)timeline-effect-bgTimeline: an effect item.accent-subtlecamellia-600-a10rgba(194,51,95,0.1)accent-subtlecamellia-400-a14rgba(244,119,159,0.14)timeline-selectedTimeline: the selected item.accentcamellia-600#c2335faccentcamellia-400#f4779ftimeline-gap-borderTimeline: a planned gap with no picture yet.border-placeholdershade-a24rgba(11,10,16,0.24)border-placeholderwhite-a22rgba(255,255,255,0.22)timeline-label-bgTimeline: label plate over a picture.bg-scrim-strongscrim-a78rgba(7,7,10,0.78)bg-scrim-strongscrim-a78rgba(7,7,10,0.78)timeline-label-fgTimeline: label over a picture.text-on-medianeutral-0#fffffftext-on-medianeutral-0#fffffftimeline-playheadTimeline: the playhead.accentcamellia-600#c2335faccentcamellia-400#f4779ftimeline-video-edgeTimeline: the edge of a video item.border-dividershade-a12rgba(11,10,16,0.12)border-dividerwhite-a12rgba(255,255,255,0.12)timeline-dialogue-bgTimeline: a line of dialogue.bg-fill-subtleshade-a06rgba(11,10,16,0.06)bg-fill-subtlewhite-a08rgba(255,255,255,0.08)timeline-music-bgTimeline: music.bg-fill-subtleshade-a06rgba(11,10,16,0.06)bg-fill-subtlewhite-a08rgba(255,255,255,0.08)timeline-audio-waveTimeline: sound waveform.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7timeline-dialogue-waveTimeline: dialogue waveform.text-secondaryneutral-800#2e2d36text-secondaryneutral-200#dcdcdetimeline-music-waveTimeline: music waveform.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7timeline-caption-fgTimeline: caption text.text-secondaryneutral-800#2e2d36text-secondaryneutral-200#dcdcdetimeline-effect-fgTimeline: effect name.text-secondaryneutral-800#2e2d36text-secondaryneutral-200#dcdcdetl-lift-shadowTimeline: the shadow under a track being moved.black-a50rgba(0,0,0,0.5)black-a50rgba(0,0,0,0.5)Space and shape
timeline-radiusradius-md12px
timeline-inset-ring-widthline-hairline1px
timeline-ruler-heightits own value
24px
timeline-ruler-border-bottom-widthline-hairline1px
timeline-head-widthits own value
120px
timeline-head-gapgap-sm6px
timeline-head-padding-xinset-sm8px
timeline-head-border-right-widthline-hairline1px
timeline-tick-padding-topinset-xs4px
timeline-tick-padding-leftinset-xs4px
timeline-tick-border-left-widthline-hairline1px
timeline-marker-topinset-2xs2px
timeline-marker-line-widthline-hairline1px
timeline-marker-gapgap-xs4px
timeline-marker-rail-heightits own value
calc(16px + 2 * 2px + 2 * 2px)
timeline-marker-padding-yinset-2xs2px
timeline-marker-padding-xinset-xs4px
timeline-marker-radiusradius-xs4px
timeline-row-heightsize-control-md44px
timeline-row-border-bottom-widthline-hairline1px
timeline-item-insetinset-xs4px
timeline-item-radiusradius-xs4px
timeline-item-inset-ring-widthline-hairline1px
timeline-item-selected-inset-ring-widthline-strong2px
timeline-item-video-border-left-widthline-heavy3px
timeline-item-video-inset-ring-widthline-hairline1px
timeline-item-video-selected-border-left-widthline-heavy3px
timeline-item-video-selected-inset-ring-widthline-strong2px
timeline-item-gap-inset-ring-widthline-strong2px
timeline-item-board-opacityopacity-muted0.6
timeline-wave-waveform-inset-topinset-xs4px
timeline-wave-waveform-inset-xinset-xs4px
timeline-wave-waveform-inset-bottominset-lg16px
timeline-label-margin-xgap-xs4px
Aa
timeline-label-margin-bottomgap-2xs2px
Aa
timeline-label-padding-xinset-xs4px
Aa
timeline-label-radiusradius-xs4px
timeline-playwrap-leftits own value
92px
timeline-playhead-topits own value
calc(-1 * 24px)
timeline-playhead-border-left-widthline-strong2px
timeline-item-focus-outline-offsetfocus-ring-offset3px
timeline-drag-focus-outline-offsetfocus-ring-offset3px
timeline-head-icon-sizeicon-size-xs14px
timeline-grip-widthits own value
8px
timeline-grip-after-widthline-strong2px
timeline-grip-after-radiusradius-full9999px
timeline-grip-after-opacityopacity-muted0.6
timeline-grip-in-radiusradius-xs4px
timeline-grip-out-radiusradius-xs4px
timeline-item-label-selected-margin-leftgap-lg12px
Aa
timeline-item-label-selected-margin-rightgap-lg12px
Aa
timeline-ghost-insetinset-xs4px
timeline-ghost-radiusradius-xs4px
timeline-ghost-border-widthline-hairline1px
timeline-drag-widthits own value
8px
timeline-drag-heightits own value
12px
timeline-drag-margin-rightgap-2xs2px
timeline-drag-border-left-widthline-strong2px
timeline-drag-border-right-widthline-strong2px
timeline-row-lifted-translate-yits own value
calc(-1 * 4px)
timeline-row-lifted-inset-ring-widthline-strong2px
timeline-row-lifted-radiusradius-sm8px
timeline-drop-ring-widthline-hairline1px
timeline-lane-grip-insetinset-xs4px
timeline-lane-grip-widthits own value
24px
timeline-lane-grip-before-widthits own value
8px
timeline-lane-grip-in-before-radiusradius-xs4px
timeline-lane-grip-in-after-leftinset-2xs2px
timeline-lane-grip-out-before-radiusradius-xs4px
timeline-lane-grip-out-after-rightinset-2xs2px
timeline-grip-focus-outline-offsetfocus-ring-offset3px
timeline-forced-edge-widthline-hairline1px
timeline-forced-selected-widthline-strong2px
timeline-forced-drop-widthline-strong2px
timeline-drag-target-widthspace-624px
timeline-drag-target-heighttimeline-row-height44px
timeline-snap-distancespace-1-56px
timeline-snap-widthline-hairline1px
Type
timeline-head-fonttext-small-strong600 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
timeline-head-ruler-font-weightweight-bold700
timeline-tick-fonttext-small-strong600 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
timeline-marker-fonttext-micro700 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
timeline-label-fonttext-small-strong600 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
Motion
timeline-row-durationmotion-fast160ms
timeline-row-easingease-standardcubic-bezier(0.2, 0, 0, 1)
timeline-row-lifted-translate-xmotion-nudge4px
timeline-item-durationmotion-instant100ms
timeline-item-easingease-entercubic-bezier(0, 0, 0, 1)
Checked automatically with axe-core 4.11.4 against WCAG 2.2 A and AA, on the live preview above, in light and dark, on 26 October 2026.
- Result
- No issues found, in light or in dark.
- By design
- A group named by
label(default "Timeline") whose ruler times, track names, markers and playhead ("Playhead at 0:04") read as text, with waveforms and icons hidden. WithonSelectthe items are one Tab stop of buttons named by label, track, times and state (Left and Right move within a track, Home and End to its ends, Up and Down to the nearest item on the next one, Enter or Space selects), a selected item's grips are sliders ("Trim the start of" the item) that move a frame per arrow (trimStep) and a second with Shift throughonTrim, and withreorderableandonMoveTrackUp and Down move a track by its handle, named with its place ("Move the Picture track, 1 of 3"), and Enter or Space lift it to move it further and drop it (Escape puts it back), each keyboard move said aloud, and a dragged track when it drops. - Not tested
- The automated check doesn't test keyboard use, focus order or what a screen reader says. The lines above are the design system's own account of them.