Time track
ProA time axis with swimlanes, point and span events, a playhead, zoom, pan and range selection, for incident reviews, signal timelines and playback.
npx prodular-ui add time-trackPart of Prodular UI Pro. Preview and read the docs freely, install with a license. See pricing
Incident review
Deploys, alerts and on-call actions on three lanes. Play moves the playhead; Shift and drag selects the window to report on.
Ctrl or Cmd and scroll to zoom, Shift and scroll or drag to pan, Shift and drag to select a range. Focus the playhead and use the arrow keys.
import * as React from "react"import { PauseIcon, PlayIcon } from "lucide-react"
import { Button } from "@/components/ui/button"import { Legend, LegendItem } from "@/components/ui/legend"import { TimeTrack, TimeTrackAxis, TimeTrackEvent, TimeTrackLane, TimeTrackLanes } from "@/components/ui/time-track"
// An incident window on October 21, 2026, 14:00 to 16:00 UTC.const start = Date.UTC(2026, 9, 21, 14, 0)const end = Date.UTC(2026, 9, 21, 16, 0)const at = (min: number) => start + min * 60_000
const lanes = [ { label: "Deploys", events: [ { id: "d1", at: at(12), label: "api-gateway 4.18.0", color: "chart-1" }, { id: "d2", at: at(71), label: "Rollback to 4.17.3", color: "chart-1" }, ], }, { label: "Alerts", events: [ { id: "a1", at: at(18), label: "Error rate above 2%", color: "severity-high" }, { id: "a2", at: at(24), end: at(66), label: "Checkout latency over budget", color: "severity-critical" }, { id: "a3", at: at(80), label: "Error rate back to normal", color: "severity-routine" }, ], }, { label: "On-call", events: [ { id: "o1", at: at(20), label: "Paged Maya Okafor", color: "chart-3" }, { id: "o2", at: at(31), label: "Incident declared", color: "chart-3" }, { id: "o3", at: at(95), label: "Incident resolved", color: "chart-3" }, ], },]
export default function TimeTrackIncident() { const [view, setView] = React.useState<[number, number]>([start, end]) const [time, setTime] = React.useState(at(24)) const [range, setRange] = React.useState<[number, number] | null>([at(24), at(66)]) const [selected, setSelected] = React.useState("a2") const [playing, setPlaying] = React.useState(false)
React.useEffect(() => { if (!playing) return const t = setInterval(() => setTime((v) => (v >= end ? start : v + 30_000)), 100) return () => clearInterval(t) }, [playing])
return ( <div className="flex w-full max-w-3xl flex-col gap-3"> <div className="flex items-center gap-2"> <Button variant="outline" size="icon-sm" aria-label={playing ? "Pause" : "Play"} onClick={() => setPlaying(!playing)}> {playing ? <PauseIcon /> : <PlayIcon />} </Button> <Button variant="ghost" size="sm" onClick={() => setView([start, end])}> Fit to incident </Button> <Legend className="ml-auto"> <LegendItem color="severity-critical" shape="diamond" label="Critical" /> <LegendItem color="severity-high" shape="triangle" label="High" /> <LegendItem color="severity-routine" shape="circle" label="Routine" /> </Legend> </div> <TimeTrack aria-label="Incident timeline" domain={[start, end]} view={view} onViewChange={setView} time={time} onTimeChange={setTime} range={range} onRangeChange={setRange} className="rounded-lg border bg-card" > <TimeTrackAxis /> <TimeTrackLanes> {lanes.map((lane) => ( <TimeTrackLane key={lane.label} label={lane.label} className="pl-3"> {lane.events.map((e) => ( <TimeTrackEvent key={e.id} at={e.at} end={e.end} label={e.label} color={e.color} selected={selected === e.id} onSelect={() => setSelected(e.id)} /> ))} </TimeTrackLane> ))} </TimeTrackLanes> </TimeTrack> <p className="text-(length:--text-ui) text-muted-foreground"> Ctrl or Cmd and scroll to zoom, Shift and scroll or drag to pan, Shift and drag to select a range. Focus the playhead and use the arrow keys. </p> </div> )}Anatomy
<TimeTrack aria-label="Incident" domain={[start, end]} time={time} onTimeChange={setTime}> <TimeTrackAxis /> <TimeTrackLanes> <TimeTrackLane label="Alerts"> <TimeTrackEvent at={t} label="Error rate above 2%" color="severity-high" /> </TimeTrackLane> </TimeTrackLanes></TimeTrack>Guidance
Use it when
- Events that happen at a time across a few sources: deploys, alerts, sensor readings, messages.
- A playhead when the rest of the screen shows the state at one moment.
- Range selection to filter other panels to a window.
Avoid
- The activity feed of a record. That is Timeline.
- More than about ten lanes. Group sources or let people pick lanes.
- Color as the only difference between events. Use labels, lanes and a Legend with shapes.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Pass times as numbers in milliseconds (Date.getTime()).
- Give every TimeTrackEvent a label. It names the event for screen readers and shows on hover.
- Color events with tokens, for example color="chart-2" or color="severity-high". Never raw colors.
- Control view, time and range when other panels depend on them; leave them uncontrolled otherwise.
- Do not hijack plain scrolling. Zoom stays on Ctrl or Cmd and the wheel, which is how TimeTrack works.
- Put a Legend next to the track when colors or shapes carry meaning.
- Pass timeZone="UTC" (or the zone your data is in) when labels must match logs or render on the server; otherwise labels use the viewer's zone and render after hydration.
API
TimeTrack
| Prop | Type | Default | Description |
|---|---|---|---|
| domain | [number, number] | The full time extent in ms. The view never leaves it. | |
| view | [number, number] | The visible window. Controlled with onViewChange, or defaultView. | |
| time | number | null | Playhead position. Controlled with onTimeChange, or defaultTime. | |
| range | [number, number] | null | Selected range. Controlled with onRangeChange, or defaultRange. | |
| minSpan | number | 10000 | Shortest span the view zooms to, in ms. |
| labelWidth | string | "7rem" | Width of the lane label column. |
| timeZone | string | IANA zone for labels, such as "UTC". Without it labels use the viewer's zone and appear after hydration. |
TimeTrackLanes
| Prop | Type | Default | Description |
|---|---|---|---|
| now | number | Draws a dashed now line. |
TimeTrackEvent
| Prop | Type | Default | Description |
|---|---|---|---|
| at | number | Start time. Add end for a span. | |
| color | string | "chart-1" | A token name. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add time-track# or, with the registry in components.jsonnpx shadcn@latest add @prodular/time-track