A rail of marks, one per chapter, that swell under the pointer like the macOS
Dock. The tallest mark, the crest, previews its chapter, so people can jump
around a long piece without a table of contents.
Why a design system
00:00
The problem: four products, four button styles, no shared names.
Chapters sit on the rail in array order, one mark each. The title and
description name each mark for screen readers.
Each chapter takes:
type Chapter = { id: string title: string description?: React.ReactNode // clamped to three lines meta?: React.ReactNode // a small label beside the title, such as a timestamp}
A long read sits to the right of a quiet rail. At rest it is a column of hairlines; under the pointer the marks swell and name the section, so the reader sees the whole shape of the piece and can jump without a table of contents taking up the margin.
Where the preview opens. Vertical rails take left or right, horizontal rails top or bottom. Unset picks right or top, and it flips when the preferred side would leave the viewport.
align
"edge" | "center"
"edge"
Ticks grow from the rail's edge, or out from its centre line.
preview
"card" | "label" | "none"
"card"
What shows beside the crest.
rowSize
number
Row pitch along the rail, in pixels.
restLength
number
Resting mark length (or dot diameter), in pixels.
peakLength
number
Mark length (or dot diameter) at the crest, in pixels.
radius
number
4
How far the wave reaches from the pointer, in rows.
currentIndex
number
The chapter marked as current. Pass it to control the marker.
defaultCurrentIndex
number
The starting current chapter when uncontrolled.
onCurrentIndexChange
(index: number, chapter: Chapter) => void
Fires when a chapter is chosen, with the new current index.
onActiveChange
(chapter: Chapter | null, index: number) => void
Fires as the hovered or focused chapter changes; null on leave.