@trailvine/react
A <Timeline /> component that renders real JSX/SVG directly — no
dangerouslySetInnerHTML, no dependency on @trailvine/vanilla — plus a
useTimelineLayout hook for anyone who wants the raw geometry instead of the packaged component.
Both accept every field from the shared options reference
(points, layout, path, wrap?, mobileBehavior?) —
this page only covers what's added on top.
<Timeline />
function Timeline<T = {}>(props: TimelineProps<T>): JSX.Element;
interface TimelineProps<T = {}> extends TrailvineOptions<T> {
className?: string;
style?: CSSProperties;
onPointHover?: (point: ExtendedTimelinePoint<T>, index: number) => void;
caption?: string;
captionPosition?: 'top' | 'bottom'; // default: 'top'
} | Prop | Notes |
|---|---|
className / style | Passed straight through to the outer <svg>. |
onPointHover | Fires on both onMouseEnter and onFocus, so keyboard users trigger it too, not just mouse users. |
caption | Optional free-text label — e.g. "Sprint 14". Space for it is reserved on the canvas whether or not it's actually set, so adding/removing one never shifts the layout. |
captionPosition | 'top' (centered, horizontal orientation) or 'bottom'. In 'vertical' orientation the caption is left-anchored instead. |
Colors
The path and points aren't styled via props — they read two CSS custom properties, with sensible defaults, from whatever ancestor element sets them:
<div style={{ '--trailvine-path-color': '#4A5568', '--trailvine-point-color': '#D4A24C' }}>
<Timeline points={points} layout={layout} path={path} />
</div> Multi-instance safety
Every rendered <Timeline /> gets its own useId()-scoped class/id prefix, so two
instances on the same page never collide — verified by an actual test rendering two as siblings and confirming
they get distinct IDs, not just by code review.
useTimelineLayout
Returns raw computed geometry without rendering anything — for building your own markup around Trailvine's math.
function useTimelineLayout<T = {}>(options: TrailvineOptions<T>): TimelineLayoutResult;
interface TimelineLayoutResult {
path: string | string[];
points: Point[];
}
Memoized by serializing options to a string — it recomputes when the options'
content changes, not just when the object reference changes. wrap.measureFn (a function)
is dropped by that serialization, which is harmless here since this hook never calls wrapText itself.
Requirements
Peer dependency: react and react-dom >=18.0.0. This is a hard
requirement, not just a recommendation — useId() (used for the multi-instance safety above)
doesn't exist in React 17.
Next steps
- Options reference — every field on
layout,path, andwrap. - Vanilla API — the equivalent plain-JS binding.
- Playground — tune every option live and copy the result as a real
<Timeline />snippet.