Sakuga Back to Sakuga
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).

@anicanon/sakuga/uiv1.0Used onWeb

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

  • duration and every item's start and length are in seconds. The ruler steps itself (1, 5 or 30 s) unless step is set; rate names 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 selected item 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; onTrim gets each new time. While trimming, pass trimmed ({ start, end } in seconds) to draw the cut-off footage as a dashed ghost beyond the item.
  • markers name 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. playhead draws 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 gap has a dashed edge. With no label, it says what it is: "No picture yet", or "Storyboard" for a board item.
  • 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.
  • reorderable gives 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, and onMoveTrack(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, music and audio (sound) draw the core Waveform at the item's width, in their own wave tokens; pass an item's peaks for 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 calls onTrim(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. Without onTrim the grips only draw.
  • onMoveTrack(from, to): with reorderable, 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.
TimelineExample.jsxReact
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

durationrequired
number
tracksrequired
TimelineTrack[]
playhead
number
offset
number
step
number
rate
string
label
string
onSelect
(track: number, item: number) => void
Makes 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) => void
A 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.
trimStep
number
What 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) => void
With 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; }[]
reorderable
boolean
Track 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.
className
string

Types

interface TimelineTrack {
    label: string;
    kind: TimelineTrackKind;
    items: TimelineItem[];
}
TimelineExample.swiftiOS · coming soon
TimelineExample.ktAndroid · coming soon

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#ffffff
bg-surfaceneutral-900#111117
timeline-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#5c5b67
text-mutedneutral-400#a3a3a7
timeline-rulerTimeline: time ticks.
text-mutedneutral-600#5c5b67
text-mutedneutral-400#a3a3a7
timeline-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#b42354
text-accentcamellia-400#f4779f
timeline-item-bgTimeline: a video item without picture.
bg-surface-raisedneutral-100#ececf0
bg-surface-raisedneutral-875#1a1a21
timeline-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#5c5b67
text-mutedneutral-400#a3a3a7
timeline-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#c2335f
accentcamellia-400#f4779f
timeline-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#ffffff
text-on-medianeutral-0#ffffff
timeline-playheadTimeline: the playhead.
accentcamellia-600#c2335f
accentcamellia-400#f4779f
timeline-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#5c5b67
text-mutedneutral-400#a3a3a7
timeline-dialogue-waveTimeline: dialogue waveform.
text-secondaryneutral-800#2e2d36
text-secondaryneutral-200#dcdcde
timeline-music-waveTimeline: music waveform.
text-mutedneutral-600#5c5b67
text-mutedneutral-400#a3a3a7
timeline-caption-fgTimeline: caption text.
text-secondaryneutral-800#2e2d36
text-secondaryneutral-200#dcdcde
timeline-effect-fgTimeline: effect name.
text-secondaryneutral-800#2e2d36
text-secondaryneutral-200#dcdcde
tl-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-radius
radius-md
12px
timeline-inset-ring-width
line-hairline
1px
timeline-ruler-height
its own value
24px
timeline-ruler-border-bottom-width
line-hairline
1px
timeline-head-width
its own value
120px
timeline-head-gap
gap-sm
6px
timeline-head-padding-x
inset-sm
8px
timeline-head-border-right-width
line-hairline
1px
timeline-tick-padding-top
inset-xs
4px
timeline-tick-padding-left
inset-xs
4px
timeline-tick-border-left-width
line-hairline
1px
timeline-marker-top
inset-2xs
2px
timeline-marker-line-width
line-hairline
1px
timeline-marker-gap
gap-xs
4px
timeline-marker-rail-height
its own value
calc(16px + 2 * 2px + 2 * 2px)
timeline-marker-padding-y
inset-2xs
2px
timeline-marker-padding-x
inset-xs
4px
timeline-marker-radius
radius-xs
4px
timeline-row-height
size-control-md
44px
timeline-row-border-bottom-width
line-hairline
1px
timeline-item-inset
inset-xs
4px
timeline-item-radius
radius-xs
4px
timeline-item-inset-ring-width
line-hairline
1px
timeline-item-selected-inset-ring-width
line-strong
2px
timeline-item-video-border-left-width
line-heavy
3px
timeline-item-video-inset-ring-width
line-hairline
1px
timeline-item-video-selected-border-left-width
line-heavy
3px
timeline-item-video-selected-inset-ring-width
line-strong
2px
timeline-item-gap-inset-ring-width
line-strong
2px
timeline-item-board-opacity
opacity-muted
0.6
timeline-wave-waveform-inset-top
inset-xs
4px
timeline-wave-waveform-inset-x
inset-xs
4px
timeline-wave-waveform-inset-bottom
inset-lg
16px
timeline-label-margin-x
gap-xs
4px
Aa
timeline-label-margin-bottom
gap-2xs
2px
Aa
timeline-label-padding-x
inset-xs
4px
Aa
timeline-label-radius
radius-xs
4px
timeline-playwrap-left
its own value
92px
timeline-playhead-top
its own value
calc(-1 * 24px)
timeline-playhead-border-left-width
line-strong
2px
timeline-item-focus-outline-offset
focus-ring-offset
3px
timeline-drag-focus-outline-offset
focus-ring-offset
3px
timeline-head-icon-size
icon-size-xs
14px
timeline-grip-width
its own value
8px
timeline-grip-after-width
line-strong
2px
timeline-grip-after-radius
radius-full
9999px
timeline-grip-after-opacity
opacity-muted
0.6
timeline-grip-in-radius
radius-xs
4px
timeline-grip-out-radius
radius-xs
4px
timeline-item-label-selected-margin-left
gap-lg
12px
Aa
timeline-item-label-selected-margin-right
gap-lg
12px
Aa
timeline-ghost-inset
inset-xs
4px
timeline-ghost-radius
radius-xs
4px
timeline-ghost-border-width
line-hairline
1px
timeline-drag-width
its own value
8px
timeline-drag-height
its own value
12px
timeline-drag-margin-right
gap-2xs
2px
timeline-drag-border-left-width
line-strong
2px
timeline-drag-border-right-width
line-strong
2px
timeline-row-lifted-translate-y
its own value
calc(-1 * 4px)
timeline-row-lifted-inset-ring-width
line-strong
2px
timeline-row-lifted-radius
radius-sm
8px
timeline-drop-ring-width
line-hairline
1px
timeline-lane-grip-inset
inset-xs
4px
timeline-lane-grip-width
its own value
24px
timeline-lane-grip-before-width
its own value
8px
timeline-lane-grip-in-before-radius
radius-xs
4px
timeline-lane-grip-in-after-left
inset-2xs
2px
timeline-lane-grip-out-before-radius
radius-xs
4px
timeline-lane-grip-out-after-right
inset-2xs
2px
timeline-grip-focus-outline-offset
focus-ring-offset
3px
timeline-forced-edge-width
line-hairline
1px
timeline-forced-selected-width
line-strong
2px
timeline-forced-drop-width
line-strong
2px
timeline-drag-target-width
space-6
24px
timeline-drag-target-height
timeline-row-height
44px
timeline-snap-distance
space-1-5
6px
timeline-snap-width
line-hairline
1px

Type

timeline-head-font
text-small-strong
600 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
timeline-head-ruler-font-weight
weight-bold
700
timeline-tick-font
text-small-strong
600 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
timeline-marker-font
text-micro
700 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
timeline-label-font
text-small-strong
600 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif

Motion

timeline-row-duration
motion-fast
160ms
timeline-row-easing
ease-standard
cubic-bezier(0.2, 0, 0, 1)
timeline-row-lifted-translate-x
motion-nudge
4px
timeline-item-duration
motion-instant
100ms
timeline-item-easing
ease-enter
cubic-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. With onSelect the 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 through onTrim, and with reorderable and onMoveTrack Up 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.