# Tempis Timeline
> A fast, zero-dependency, canvas-rendered timeline library for the web.
- Website: https://tempis.dev
- GitHub: https://github.com/tempis-dev/tempis
- Licence: Source-available (free non-commercial, paid commercial)
## Install
```bash
npm install @tempis/timeline
```
React wrapper:
```bash
npm install @tempis/react @tempis/timeline
```
CDN:
```html
```
## Constructor
```typescript
new TempisTimeline(context: string | HTMLCanvasElement, options: TempisTimelineOptions)
```
## TempisTimelineOptions
```typescript
interface TempisTimelineOptions {
items: TempisTimelineItem[]; // Required. The items to display.
responsive?: boolean; // Resize canvas to match parent. Default: false.
verticalFill?: "content" | "fill-canvas" | "grow-canvas"; // Default: "content".
rtl?: boolean; // Right-to-left layout. Default: false.
stackMode?: "compact" | "stable"; // Item stacking. Default: "stable".
selection?: "none" | "single" | "multi"; // Selection mode. Default: "none".
categories?: TempisTimelineCategory[];
bands?: TempisTimelineBand[];
dependencies?: TempisTimelineDependency[];
range?: TempisTimelineRangeOptions;
legend?: TempisTimelineLegendOptions;
tooltip?: TempisTimelineTooltipOptions;
style?: TempisTimelineStyleOptions;
scrollbar?: TempisTimelineScrollbarOptions;
grouping?: TempisTimelineGroupingOptions;
accessibility?: TempisTimelineAccessibilityOptions;
minimap?: TempisTimelineMinimapOptions;
// Callbacks
onItemClick?(id: string | number): void;
onItemDoubleClick?(id: string | number): void;
onItemContextClick?(id: string | number, position: { x: number; y: number }): void;
onItemHover?(id: string | number | null): void;
onSelectionChange?(changes: SelectionChangeEvent[]): void;
onRangeChange?(start: Date, end: Date): void;
onGroupToggle?(group: string, collapsed: boolean): void;
}
```
## TempisTimelineItem
```typescript
interface TempisTimelineItem {
id: string | number; // Required. Unique identifier.
start: string | number | Date; // Required. ISO string, timestamp, or Date.
end?: string | number | Date; // Omit for point-in-time items.
label?: string; // Display label.
grouping?: string; // Group name for row organisation.
category?: string; // Category name (must match a defined category).
style?: TempisTimelineItemStyle; // Per-item style overrides.
selected?: boolean; // Whether initially selected.
progress?: number; // Completion progress (0–1). Renders as fill bar.
}
```
## TempisTimelineItemStyle
```typescript
interface TempisTimelineItemStyle {
backgroundColor?: string;
fontColor?: string;
padding?: number;
borderColor?: string;
borderThickness?: number;
borderStyle?: "solid" | "dashed" | "dotted" | "dash-dot" | "long-dash";
borderRadius?: number;
}
```
## TempisTimelineCategory
```typescript
interface TempisTimelineCategory {
name: string; // Unique identifier referenced by items.
label: string; // Display label in the legend.
style?: TempisTimelineItemStyle; // Style applied to all items in this category.
}
```
## TempisTimelineBand
```typescript
interface TempisTimelineBand {
start: string | number | Date; // Required.
end?: string | number | Date; // Omit for point-in-time band (vertical line).
style?: {
color?: string;
borderColor?: string;
borderThickness?: number;
opacity?: number;
};
}
```
## TempisTimelineDependency
```typescript
interface TempisTimelineDependency {
source: string | number; // Source item ID (arrow starts here).
target: string | number; // Target item ID (arrow points here).
style?: {
color?: string;
lineWidth?: number;
lineStyle?: "solid" | "dashed" | "dotted" | "dash-dot" | "long-dash";
};
}
```
## TempisTimelineRangeOptions
```typescript
interface TempisTimelineRangeOptions {
fixed?: boolean; // Lock range (no pan/zoom). Default: false.
position?: "top" | "bottom" | "both" | "none"; // Axis position. Default: "bottom".
start?: string | number | Date; // Initial visible start.
end?: string | number | Date; // Initial visible end.
min?: string | number | Date; // Earliest navigable date.
max?: string | number | Date; // Latest navigable date.
minorUnit?: { font?: TempisTimelineFont; formats?: object };
majorUnit?: { font?: TempisTimelineFont; formats?: object };
zoom?: {
enabled?: boolean; // Default: true.
min?: number; // Minimum range in ms.
max?: number; // Maximum range in ms.
wheelSensitivity?: number; // Default: 1.0.
pinchSensitivity?: number; // Default: 1.0.
};
}
```
## TempisTimelineLegendOptions
```typescript
interface TempisTimelineLegendOptions {
position?: "top" | "bottom" | "none"; // Default: "none".
alignment?: "start" | "center" | "end"; // Default: "start".
markerStyle?: "square" | "square-rounded" | "circle"; // Default: "square-rounded".
isHighlightOnHover?: boolean; // Default: true.
isFilterOnClick?: boolean; // Default: true.
gap?: number;
}
```
## TempisTimelineTooltipOptions
```typescript
interface TempisTimelineTooltipOptions {
enabled?: boolean; // Default: true.
delay?: number; // Delay in ms. Default: 0.
dateFormat?: string; // Default: "D MMMM HH:mm:ss".
overflowBehavior?: "none" | "canvas" | "viewport"; // Default: "none".
template?: (id: string | number) => HTMLElement | string | null;
shouldShow?: (id: string | number) => boolean;
}
```
## TempisTimelineStyleOptions
```typescript
interface TempisTimelineStyleOptions {
font?: TempisTimelineFont;
item?: TempisTimelineItemStyle;
gridColor?: string; // Colour for grid lines, labels, separators. Default: "#808080".
}
```
## TempisTimelineFont
```typescript
interface TempisTimelineFont {
family?: string; // Default: "Helvetica, Arial, sans-serif".
size?: number; // Default: 14.
weight?: string | number; // Default: "normal".
style?: string; // Default: "normal".
lineHeight?: string;
}
```
## TempisTimelineScrollbarOptions
```typescript
interface TempisTimelineScrollbarOptions {
visibility?: "always" | "hover" | "panning" | "never"; // Default: "hover".
color?: string; // Thumb/track colour.
}
```
## TempisTimelineGroupingOptions
```typescript
interface TempisTimelineGroupingOptions {
sort?: (a: string, b: string) => number;
collapsible?: boolean; // Default: false.
}
```
## TempisTimelineAccessibilityOptions
```typescript
interface TempisTimelineAccessibilityOptions {
ariaLabel?: string; // Sets aria-label on the canvas.
keyboard?: boolean; // Enable keyboard nav. Default: true.
keyboardPanStep?: number; // Fraction of range per arrow key. Default: 0.1.
keyboardZoomStep?: number; // Zoom intensity per +/- key. Default: 0.5.
}
```
## TempisTimelineMinimapOptions
```typescript
interface TempisTimelineMinimapOptions {
height?: number; // Default: 40.
backgroundColor?: string;
viewportColor?: string;
}
```
## Instance Methods
```typescript
// Items
setItems(items: TempisTimelineItem[]): void
getItems(): TempisTimelineItem[]
// Categories
setCategories(categories: TempisTimelineCategory[]): void
getCategories(): TempisTimelineCategory[]
// Bands
setBands(bands: TempisTimelineBand[]): void
// Dependencies
setDependencies(dependencies: TempisTimelineDependency[]): void
// Selection
setSelection(ids: (string | number)[]): void
getSelection(): (string | number)[]
clearSelection(): void
// Groups
setGroupCollapsed(group: string, collapsed?: boolean): void // Omit collapsed to toggle.
isGroupCollapsed(group: string): boolean
// Navigation
focus(options?: FocusOptions): void
getRange(): { start: Date; end: Date }
// Export
toImage(options?: ImageGenerationOptions): Promise
// Lifecycle
redraw(): void
destroy(): void
```
## FocusOptions
```typescript
interface FocusOptions {
id?: string | number; // Item to focus on.
date?: string | number | Date; // Date to centre on.
range?: [DateInput, DateInput]; // Range to display.
animate?: boolean; // Default: false.
duration?: number; // Animation ms. Default: 500.
easing?: "linear" | "easeIn" | "easeOut" | "easeInOut" | "easeInCubic" | "easeOutCubic" | "easeInOutCubic";
zoom?: boolean | "auto"; // Default: "auto".
}
```
## ImageGenerationOptions
```typescript
interface ImageGenerationOptions {
type?: string; // "image/png" | "image/jpeg" | "image/webp". Default: "image/png".
quality?: number; // 0–1 for lossy formats.
dpr?: number; // Device pixel ratio. Default: 1.
backgroundColor?: string; // Default: transparent.
}
```
## SelectionChangeEvent
```typescript
interface SelectionChangeEvent {
id: string | number;
selected: boolean;
}
```
## Static Methods
```typescript
TempisTimeline.setGlobalPalette(colors: string[]): void
TempisTimeline.getGlobalPalette(): string[]
TempisTimeline.registerDateAdapter(adapter: TempisTimelineDateAdapter): void
```
## Date Adapter Interface
```typescript
interface TempisTimelineDateAdapter {
parse(input: string | number | Date): number | null;
startOf(instant: number, unit: string): number;
add(instant: number, unit: string, amount: number): number;
format(instant: number, pattern: string): string;
}
```
Units: year, month, week, day, hour, minute, second, millisecond
Format tokens: YYYY, MMMM, MMM, MM, D, ddd, HH, hh, mm, ss, SSS, A, a
## React Component
```tsx
import { TempisTimeline } from '@tempis/react';
{}}
onSelectionChange={(changes) => {}}
onGroupToggle={(group, collapsed) => {}}
ref={timelineRef}
height={400}
/>
```
Reactive props (synced without recreate): items, categories, bands, dependencies
Structural props (trigger recreate on change): options
## Ref API (React)
```typescript
interface TempisTimelineRef {
focus(options?: FocusOptions): void;
getRange(): { start: Date; end: Date };
setItems(items: TempisTimelineItem[]): void;
getItems(): TempisTimelineItem[];
setCategories(categories: TempisTimelineCategory[]): void;
getCategories(): TempisTimelineCategory[];
setBands(bands: TempisTimelineBand[]): void;
setDependencies(deps: TempisTimelineDependency[]): void;
setSelection(ids: (string | number)[]): void;
getSelection(): (string | number)[];
clearSelection(): void;
setGroupCollapsed(group: string, collapsed?: boolean): void;
isGroupCollapsed(group: string): boolean;
toImage(options?: ImageGenerationOptions): Promise;
redraw(): void;
getInstance(): CoreTimeline | null;
getCanvas(): HTMLCanvasElement | null;
}
```
## Quick Example
```typescript
import { TempisTimeline } from '@tempis/timeline';
const timeline = new TempisTimeline('#canvas', {
responsive: true,
verticalFill: 'fill-canvas',
grouping: { collapsible: true },
minimap: { height: 40 },
style: {
gridColor: '#64748b',
font: { family: "'Inter', sans-serif", size: 13 },
item: { borderRadius: 6, fontColor: '#fff', padding: 8 }
},
categories: [
{ name: 'feature', label: 'Feature', style: { backgroundColor: '#6366f1' } },
{ name: 'bug', label: 'Bug Fix', style: { backgroundColor: '#ef4444' } },
],
legend: { position: 'top', markerStyle: 'circle' },
items: [
{ id: 1, label: 'Auth Redesign', start: '2026-01-05', end: '2026-01-20', category: 'feature', grouping: 'Sprint 1', progress: 0.8 },
{ id: 2, label: 'Fix Login', start: '2026-01-10', end: '2026-01-15', category: 'bug', grouping: 'Sprint 1', progress: 1.0 },
{ id: 3, label: 'Dashboard', start: '2026-01-18', end: '2026-02-05', category: 'feature', grouping: 'Sprint 2', progress: 0.3 },
],
dependencies: [
{ source: 1, target: 3, style: { lineStyle: 'dashed' } }
],
range: { start: '2026-01-01', end: '2026-02-10', position: 'bottom' },
selection: 'single',
onItemClick(id) { console.log('clicked', id); },
onSelectionChange(changes) { console.log('selection', changes); }
});
```