Skip to content

TimelineChart

Plots work against time, with a row per thing and a bar per stretch of it. It is a Gantt chart: what is happening, on which track, and for how long.

tsx
import { TimelineChart } from 'neba';

<TimelineChart
  label="Release plan by workstream"
  series={[
    {
      name: 'Design',
      data: [{ start: new Date('2026-02-03'), end: new Date('2026-03-03'), label: 'Wireframes' }]
    }
  ]}
/>;

This is not Timeline. That one is a list of steps with no axis under it, for a sequence of events; this one draws spans against a calendar, for how long each of them took.

The data

A row is a series and a span is a datum, but a span is not a NebaChartPoint (it has two positions on the axis rather than one), so it has a type of its own.

ts
{ start: new Date('2026-03-02'), end: new Date('2026-03-16'), label: 'Wireframes' }

start and end are a Date or a number of milliseconds. A span written back to front is drawn the right way round.

Spans on one row share it. Two that overlap are given a lane each rather than being drawn over one another, so a row doing two things at once shows both; a row whose spans do not overlap keeps its full thickness.

PropTypeDefaultDescription
start * Date | numberWhen the span begins
end * Date | numberAnd when it is done. A span written back to front is drawn the right way round
labelReactNodeWhat the span is called, in the tooltip and the table
colorNebaColor | stringOverrides its row's colour for this one span

Props

PropTypeDefaultDescription
series * NebaTimelineSeries[]One row per series, and a span per datum. A row's name is what the axis says down the left
minDate | numberWhere the time axis starts. Taken from the spans otherwise, and rounded outward to a date a calendar has a name for
maxDate | numberWhere the time axis ends
xAxisNebaChartAxisThe row axis. xAxis is the category axis whichever way a chart runs
yAxisNebaChartAxisThe time axis. It is drawn along the bottom here and it is still the value axis, so it is yAxis
barSizenumbersizeHow thick a bar may get, in pixels. Below the cap the bars fill their share of the row; above it the leftover stays as air
roundedbooleantrueCuts the corners off a span. Both ends, unlike a BarChart: a span grows from nothing, so neither end is a baseline
variantshared'solid' | 'outline' | 'text''text'Weight of the surface. A chart is a drawing rather than a sheet, so this defaults to text and a chart inside a Card draws no second edge. Use outline for one that stands alone
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Axis type, line weight, marker size, and the height, when none is given
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'The sheet's colour family. A series' colour does not come from here: the palette or series.color decides that
densityshared'default' | 'compact''default'Padding only: never the height, never the type scale
elevationshared0 | 1 | 2 | 30Drop shadow depth. 0 means no shadow at all
heightnumber | stringsizeHow tall the drawing is. The axis labels are drawn inside it, so a card sized to the chart is a card the chart fits in
labelstringlocale's wordThe chart's accessible name. Read out in place of the drawing, and the caption of the hidden data table under it. Without it the locale's generic word stands in, but what the chart is *of* can only be said here
formatIntl.NumberFormatOptionsHow numbers are written everywhere they appear: the axis, the tooltip, the value labels, the table. Without it, past ten thousand they are compacted (12.4K)
localestringThe language of the chart's own words and dates
legendboolean | NebaChartLegendseries ≥ 2Shown automatically from two series up and left off below that: a legend with one swatch restates the title
tooltipboolean | NebaChartTooltiptrueWhat the pointer uncovers. It never carries a value that is not also in the hidden table
emptyReactNodeWhat to draw when there is nothing to draw

Every native <div> attribute passes through, along with every Box prop. There is no legend: a Gantt's rows are its axis, already named down the side. See prop conventions for the shared axes.

Examples

min · max

Left alone, the axis is taken from the spans and rounded outward to a date a calendar has a name for. min and max pin it (to a quarter, to a sprint, to a working day), and a span that runs past the edge is cut there rather than dragging the whole axis out to meet it.

The tick unit follows the range: seconds, minutes, hours, days, weeks, months, quarters or years. A day-long chart ticks on the hour.

barSize · rounded · density

barSize caps how thick a bar may get; below the cap the bars fill their share of the row. density changes the share and nothing else. rounded cuts the corners off a span: both ends, unlike a BarChart, because a span grows from nothing and neither of its ends is a zero.

xAxis · yAxis

xAxis is the row axis and yAxis is the time axis, which is the same rule every chart follows: xAxis is the category axis and yAxis the value axis, whichever way the chart is drawn. The time axis is along the bottom here, and it is still yAxis.

yAxis.tickFormat writes the ticks, yAxis.tickCount asks for roughly a number of them, and xAxis.hidden drops the row names.

tsx
<TimelineChart yAxis={{ tickCount: 4 }} xAxis={{ label: 'Workstream' }}  />

Colour

A row takes its palette slot from its place in the series array. series.color overrides the slot with a colour family or any CSS colour, and a span's own color overrides it for that one bar: which is how the one piece of work that is late gets to say so.

Accessibility

  • The data is also rendered as a visually hidden table, captioned with label, with one row per span under the name of the row it belongs to.
  • The plot is focusable; / walk the spans in the order the data was given, Home / End jump to the ends, Escape clears the tooltip.
  • The pointer picks the span it is inside rather than the one with the nearest centre, so a long bar is not stolen by a short neighbour.

Released under the MIT License