Home Get Started Examples API Pricing Playground GitHub

Get started

Install the package for your stack, point the timeline at a canvas element, and pass it an array of items. Everything else is optional.

Core React Vue Browser bundle

Core library

The core package has no runtime dependencies and works with any framework, or none. See the core API reference for every option, method and callback.

$ npm install @tempis/timeline

Render a timeline

The constructor takes a canvas element or a CSS selector, plus an options object. items is the only required option.

<div style="width: 100%; height: 320px;">
  <canvas id="timeline"></canvas>
</div>
import { TempisTimeline } from '@tempis/timeline';

const timeline = new TempisTimeline('#timeline', {
  responsive: true,
  range: { start: '2026-01-01', end: '2026-02-01' },
  items: [
    { id: 1, label: 'Discovery',  start: '2026-01-05', end: '2026-01-15', grouping: 'Phase 1' },
    { id: 2, label: 'Build',      start: '2026-01-12', end: '2026-01-28', grouping: 'Phase 1' },
    { id: 3, label: 'Launch',     start: '2026-01-30',                    grouping: 'Phase 1' },
    { id: 4, label: 'Docs',       start: '2026-01-08', end: '2026-01-25', grouping: 'Phase 2' }
  ]
});

Items with a start and an end render as bars. Items with only a start render as point-in-time markers. Dates accept an ISO string, epoch milliseconds, or a Date.

Sizing. With responsive: true the canvas tracks its parent element's box via a ResizeObserver. The parent needs a resolved width and height, otherwise the canvas collapses to zero. Without responsive, set the canvas dimensions yourself and call redraw() after any resize.

Categories and the legend

Categories give a set of items a shared style and an entry in the legend. Clicking a legend entry filters that category; hovering highlights it. Both behaviours can be turned off.

const timeline = new TempisTimeline('#timeline', {
  responsive: true,
  legend: { position: 'top' },
  categories: [
    { name: 'planned', label: 'Planned',   style: { backgroundColor: '#6366f1', fontColor: '#fff' } },
    { name: 'blocked', label: 'Blocked',   style: { backgroundColor: '#e11d48', fontColor: '#fff' } }
  ],
  items: [
    { id: 1, label: 'Migration',  start: '2026-02-01', end: '2026-02-14', category: 'planned' },
    { id: 2, label: 'Data audit', start: '2026-02-05', end: '2026-02-12', category: 'blocked' }
  ],
  range: { start: '2026-01-25', end: '2026-03-01' }
});

A category without an explicit backgroundColor takes the next colour from the global palette, so you can omit styling entirely and still get distinct colours.

Reference: Categories, Legend, Color palette.

Updating data

Pass replacement arrays. Each setter rebuilds what it needs to and redraws.

timeline.setItems(items);
timeline.setCategories(categories);
timeline.setBands(bands);
timeline.setDependencies(dependencies);

Reference: Items, Bands, Dependencies.

Moving the view

focus() pans and, where it makes sense, zooms. With no arguments it fits the full extent of the data.

timeline.focus({ id: 1, animate: true, duration: 500 });   // an item
timeline.focus({ date: '2026-06-15' });                    // centre on an instant
timeline.focus({ range: ['2026-01-01', '2026-12-31'] });    // fit a range
timeline.focus();                                          // fit all items

By default zoom: 'auto' only changes the zoom level when the target is a range item long enough to warrant it; point-in-time items keep the current zoom and are centred. Pass zoom: false to always preserve the zoom level. Animation is skipped automatically when the user prefers reduced motion.

Reference: Focus, Range.

Teardown

destroy() removes the canvas listeners, disconnects the resize observer, cancels animations and clears the canvas. Call it before the element leaves the DOM.

timeline.destroy();

React

@tempis/timeline is a peer dependency, so install both. See the React API reference for the full component, props and ref API.

$ npm install @tempis/react @tempis/timeline

Render a timeline

Data goes in as props. Configuration goes in options, which mirrors the core options minus the data and callbacks.

import { TempisTimeline } from '@tempis/react';

const items = [
  { id: 1, label: 'Discovery', start: '2026-01-05', end: '2026-01-15', grouping: 'Phase 1' },
  { id: 2, label: 'Build',     start: '2026-01-12', end: '2026-01-28', grouping: 'Phase 1' }
];

export function Schedule() {
  return (
    <TempisTimeline
      items={items}
      options={{ responsive: true, range: { start: '2026-01-01', end: '2026-02-01' } }}
      onItemClick={(id) => console.log('clicked', id)}
      height={400}
    />
  );
}
Changing options recreates the instance. The component compares the serialised value of options, so an inline object literal is fine, re-rendering with the same values changes nothing. But a genuine value change rebuilds the timeline and resets the current pan and zoom, so avoid driving frequently changing state through options. Callbacks and tooltip.template are exempt: they are held in a ref and can change freely.

The items, categories, bands and dependencies props are synced through the corresponding setters, so updating them does not recreate the instance.

Imperative methods

import { useRef } from 'react';
import { TempisTimeline, type TempisTimelineRef } from '@tempis/react';

export function Schedule() {
  const timeline = useRef<TempisTimelineRef>(null);

  return (
    <>
      <button onClick={() => timeline.current?.focus({ id: 1, animate: true })}>
        Focus discovery
      </button>
      <TempisTimeline ref={timeline} items={items} options={options} />
    </>
  );
}

The ref carries the same methods as the core instance, plus getInstance() and getCanvas() as escape hatches. destroy() is deliberately absent; unmounting handles it.

Reference: Ref API, Props.

Selection

Providing onSelectionChange puts selection into controlled mode: the timeline reports what the user did but does not change its own state, so you apply the result with setSelection(). Omit the callback to let it manage selection internally.

const [selected, setSelected] = useState<(string | number)[]>([]);

<TempisTimeline
  ref={timeline}
  items={items}
  options={{ ...options, selection: 'multi' }}
  onSelectionChange={(changes) => {
    const next = new Set(selected);
    changes.forEach((c) => (c.selected ? next.add(c.id) : next.delete(c.id)));
    setSelected([...next]);
    timeline.current?.setSelection([...next]);
  }}
/>

Reference: Props & selection, Selection modes.

Vue

$ npm install @tempis/vue @tempis/timeline

Requires Vue 3.3 or later. See the Vue API reference for the full component, props, events and ref API.

Render a timeline

<script setup lang="ts">
import { ref } from 'vue';
import { TempisTimeline } from '@tempis/vue';

const items = ref([
  { id: 1, label: 'Discovery', start: '2026-01-05', end: '2026-01-15', grouping: 'Phase 1' },
  { id: 2, label: 'Build',     start: '2026-01-12', end: '2026-01-28', grouping: 'Phase 1' }
]);

const options = { responsive: true, range: { start: '2026-01-01', end: '2026-02-01' } };
</script>

<template>
  <TempisTimeline
    :items="items"
    :options="options"
    :height="400"
    @item-click="(id) => console.log('clicked', id)"
  />
</template>
Replace arrays, don't mutate them. The data watchers are shallow, so items.value.push(...) will not trigger a redraw. Assign a new array, or call setItems() on the exposed ref.

Selection with v-model

Binding v-model:selection makes selection controlled and keeps the array in sync in both directions.

<script setup lang="ts">
const selected = ref<(string | number)[]>([]);
const options = { responsive: true, selection: 'multi' as const };
</script>

<template>
  <TempisTimeline v-model:selection="selected" :items="items" :options="options" />
</template>

Leave the prop off entirely and the timeline tracks selection itself.

Reference: Props & events.

Imperative methods

<script setup lang="ts">
import { ref } from 'vue';
import { TempisTimeline, type TempisTimelineExposed } from '@tempis/vue';

const timeline = ref<TempisTimelineExposed | null>(null);
</script>

<template>
  <button @click="timeline?.focus({ id: 1, animate: true })">Focus discovery</button>
  <TempisTimeline ref="timeline" :items="items" :options="options" />
</template>

Reference: Template ref API, Options.

Browser bundle

For pages without a build step, load the UMD bundle. It attaches a tempis_timeline global.

<canvas id="timeline"></canvas>

<script src="https://unpkg.com/@tempis/timeline/dist/tempis_timeline.min.js"></script>
<script>
  new tempis_timeline.TempisTimeline('#timeline', {
    responsive: true,
    range: { start: '2026-01-01', end: '2026-02-01' },
    items: [
      { id: 1, label: 'Discovery', start: '2026-01-05', end: '2026-01-15' }
    ]
  });
</script>

AdapterRegistry is on the same global if you need to register a custom date adapter.

Reference: Core API, Date adapters.

Next