2.0 alpha
These docs describe 2.0. Install it with @once-ui-system/core@alpha.
On 1.8.x? Read the 1.x docs
TrademarkTrademarkDocs
Ctrl k
Search docs...
Components

Scrubber

Updated: August 30, 2026
View on GitHub
Scrubber positions things by time rather than by layout. With no tracks it is a seek bar. Give it tracks and it is an editor timeline: stacked layers of blocks sharing one playhead, each block selectable, movable and trimmable. It is not Timeline, which lays out a sequence of steps or events down the page. Times are in whatever unit you and the component agree on — milliseconds by convention, which is what the default m:ss formatter assumes. Pass formatTime for anything else, including frames or bars. Each track is a row of blocks. movable lets the body be dragged along the track, resizable puts trim handles on the edges, and onAdd turns a click on empty track space into a new block. Blocks colour themselves from a scheme, so layers stay distinguishable. onBlockChange hands back absolute times, already clamped to [0, duration] and measured from where the gesture started — not accumulated per pointer move, so a block cannot drift away from the pointer over a long drag. Everything past that is your rule: a minimum length, no overlap with the next block, snapping to a grid. The block renders wherever you put it, so refusing a change is simply not applying it. onGestureStart fires once when a drag begins, which is the moment to snapshot for undo — one entry per gesture rather than one per pointer move. The track is a role="slider": and step the playhead by step (a hundredth of the duration by default), Home and End jump to either edge. Dragging uses pointer events, so blocks and playhead work with a mouse, a pen or a finger. Block gestures stay pointer-driven. Right-clicking a block calls onBlockContextMenu with the event and the ids — build the menu yourself, since what belongs in it is entirely your application's business.
Prop
Type
Default
durationrequired
Total length of the timeline, in seconds.
number
valuerequired
Where the playhead is, in seconds.
number
onChangerequired
Called as the playhead is dragged or the track is clicked.
(time: number) => void
tracks
The lanes under the playhead, each with its own blocks.
ScrubberTrack[][]
selected
Id of the currently selected block.
stringnull
null
onSelect
Called when a block is selected, or with null when it is deselected.
(blockId: string | null) => void
onBlockChange
Called when a block is moved or resized, with its new start and end.
(trackId: string, blockId: string, next: { start: number; end: number }) => void
onGestureStart
Called once when a drag begins — the place to pause playback.
() => void
onBlockContextMenu
Right-click on a block, with the event and what was hit.
(event: React.MouseEvent, trackId: string, blockId: string) => void
showTime
Show the current time next to the playhead.
booleantrue
formatTime
Turn seconds into the label shown. Defaults to m:ss.
(time: number) => stringdefaultFormatTime
step
Snap the playhead to this interval, in seconds.
number
ariaLabel
Label for the playhead handle.
stringSeek
…and every FlexProps propinherited
Prop
Type
Default
id
string
blocks
ScrubberBlock[]
height
SpacingToken40
scheme
neutralbrandaccentinfodangerwarningsuccess
movable
booleanfalse
resizable
booleanfalse
onAdd
(time: number) => void
Prop
Type
Default
id
string
start
number
end
number
label
ReactNode
scheme
neutralbrandaccentinfodangerwarningsuccess
locked
booleanfalse
Edit this page on GitHub
TrademarkTrademark
Built with curiosity by Lorant One and the Design Engineers Club