Migrating from vis-timeline
Most vis-timeline setups move over by changing the shape of the data and renaming a handful of options — or in one call: fromVis from @temporeum/timeline-core/vis-compat reads vis-timeline's items, groups and options by vis-timeline's own rules and returns the configuration, with a warning for everything that did not carry over. This page maps every vis-timeline option, item field, method and event that has an equivalent, and lists, honestly, the ones that don't have one yet. Each mapping in the tables below is a running example in the test suite, and each "Not yet" row is checked against the configuration's types. A row that maps to something that doesn't exist fails the build.
Live example: vis-timeline data, converted
Loading…
Run “vis-timeline data, converted” on your own machine
Make an empty folder and put this example’s two files in it:
index.html— the page the code runs in, its markup and styles:<div id="timeline" style="height: 240px"></div> <pre class="log" id="log" tabindex="0" aria-live="polite"></pre>main.js— the code, as it is in the editor now, your edits included:In a terminal in that folder, install the library and Vite (it lets the browser follow the
imports), then start it:npm init -y npm install @temporeum/timeline-element @temporeum/timeline-core npm install --save-dev vite npx viteOpen the address it prints,
http://localhost:5173/.
The plugins
vis-timeline draws everything it can draw on every page. Here the parts a plain timeline does not need are plugins, each on its own entry point, registered once before the first timeline:
import { createTimeline, registerPlugin } from "@temporeum/timeline-element";
import { itemsPlugin } from "@temporeum/timeline-element/items";
import { fromVis } from "@temporeum/timeline-core/vis-compat";
// vis-timeline draws every kind of item and lets the reader remove one:
// here those are the items plugin's. Groups, the time tools and right to
// left are plugins too (`/groups`, `/tools`, `/intl`) — register what the
// converted configuration uses.
registerPlugin(itemsPlugin);From the running example example/migration-vis-timeline
What fromVis can put in a configuration, and the plugin each needs:
| In the converted configuration | Plugin | From |
|---|---|---|
groups (from vis-timeline's groups with nestedGroups) | groupsPlugin | @temporeum/timeline-element/groups |
item kinds box (vis-timeline's kind for an item without an end), point, background; template; editing.add, editing.remove (editable: true sets both, as in vis-timeline) | itemsPlugin | @temporeum/timeline-element/items |
hiddenDates, rolling, tooltip | toolsPlugin | @temporeum/timeline-element/tools |
direction: "rtl" | intlPlugin | @temporeum/timeline-element/intl |
A converted configuration that needs a plugin that is not registered throws a TypeError where the timeline is created, naming the import that is missing — it does not draw a timeline that quietly lacks the option. When in doubt, register all four; the <script>-tag build registers every plugin on load.
What changes
- No DataSet. Items and groups are plain arrays. To change them, give the timeline the next array with
setItemsorupdate({ groups }). - Ids are strings and times are
Dateobjects or epoch milliseconds. vis-timeline also accepts date strings, and reads them with moment:"2026-03-02"is local midnight there, but UTC midnight fornew Date("2026-03-02").fromVisreads them the way vis-timeline does — in the runtime's zone, or thetimeZoneit is given — and sets that zone as the config'stimeZone, so the axis draws in it as vis-timeline's does; its tests hold it to vis-timeline 8.5.6 itself, on every example of vis-timeline's own documentation. - Groups name their parent. An item's
groupbecomeslaneId, a group'scontentbecomes itslabel, and a nested group saysparentIdinstead of its parent listingnestedGroups(groups). - Labels are text; templates are sanitised HTML. A
templateis run through the library's own sanitiser, which drops scripts and handlers, so no separate XSS filter is needed. For full control,hooks.renderItemreturns scene nodes. Both also render on the server. - Editing produces commands. A drag or a key press produces one reversible command, and
on("command")receives it. The library never saves; what happens next is up to you (editing). - The keyboard works by default. Every gesture has a key, and screen readers see the timeline as a listbox (accessibility).
The mapping
The timeline in each row's example has five items in two lanes; the row's code is the only difference.
| vis-timeline | Temporeum | Status | Notes |
|---|---|---|---|
new Timeline(container, items, groups, options) | createTimeline(container, { items, lanes, ...config }) | Renamed | Plain arrays, no DataSet. One configuration object holds everything. |
the whole setup at once | fromVis(items, groups, options) from /vis-compat | Different | @temporeum/timeline-core/vis-compat reads vis-timeline's items, groups and options by vis-timeline's own rules — date strings as moment reads them, item types, nested and hidden groups, groupOrder — and returns the configuration, and a warning for everything that did not carry over. |
DataSet add / update / remove | timeline.setItems(nextItems) | Different | Items are immutable data: hand the timeline the next array. For data that is too big to hold, items can be a function that loads a window. |
setOptions(options) | timeline.update(config) | Renamed | A key given as undefined goes back to its default; a key left out keeps its value. |
autoResize | always on | Same | The timeline follows its container's size. Below breakpoint it turns vertical when orientation is "auto". |
destroy() | timeline.destroy() | Same |
| vis-timeline | Temporeum | Status | Notes |
|---|---|---|---|
start, end | domain: [start, end] | Renamed | Without a domain the view fits the items, with a margin. |
min, max | limits: { min, max } | Renamed | |
zoomMin, zoomMax | limits: { minSpanMs, maxSpanMs } | Renamed | The shortest and longest visible span, in milliseconds. |
setWindow(start, end) | timeline.zoomTo([start, end]) | Renamed | |
getWindow() | timeline.getSnapshot().domain | Renamed | The snapshot is also what subscribe listeners read: it fits React's useSyncExternalStore. |
moveTo(time) | timeline.scrollTo(time) | Renamed | |
zoomIn(percentage), zoomOut(percentage) | timeline.zoomBy(factor, around?) | Different | A factor above 1 zooms in. around is the time that stays put; the centre by default. |
fit() | timeline.zoomTo([first, last]) | Different | There is no fit(): the first view fits the items when no domain is given; later, zoom to their extent. |
zoomable: false | interactions: { zoom: false } | Renamed | |
moveable: false | interactions: { pan: false } | Renamed | |
zoomKey | ctrl + wheel, or a pinch | Different | Fixed, not an option: a plain wheel pans or scrolls, ctrl + wheel (and a trackpad pinch, which browsers report as one) zooms. |
horizontalScroll, verticalScroll | touchAction: "cooperative" | Different | A plain wheel already pans along time and scrolls the lanes. On touch, "cooperative" leaves vertical page scrolling to the page, for a timeline inside an article. |
rollingMode | rolling: { offset } | Renamed | The view keeps its span and keeps now at offset (the middle by default), reading the clock every intervalMs. A pan or a zoom of the reader's lets go of it; follow() picks it up again. |
hiddenDates | hiddenDates: [{ start, end, repeat }] | Different | The same shape. start and end are Dates or epoch milliseconds, and a repeat is a calendar repeat in timeZone: a nightly range stays on the wall clock across a change of daylight saving. |
| vis-timeline | Temporeum | Status | Notes |
|---|---|---|---|
height | height | Same | A number of CSS pixels. Without one, the container's own height when a stylesheet gives it one. |
width, minHeight, maxHeight | the container's CSS | Different | The timeline fills its container; size the container. |
margin | density: "comfortable" | "compact" | "dense" | Different | Spacing comes in three densities, picked automatically by default to fit the items. |
orientation: "top" | "bottom" (axis position) | — | Not yet | Not to be confused with Temporeum's orientation, which is horizontal or vertical. |
stack: false | packMode: "overlap" | Renamed | "pack" (the default) is vis's stack: true; "single" gives every item its own row. Also per lane. |
align | — | Not yet | Labels start at the item's start, and move after it when they do not fit inside. |
template | template: (item) => html | Same | Sanitised by default: scripts, handlers and anything outside an allow-list are dropped (templateOptions). For full control over the SVG, hooks.renderItem returns scene nodes. |
cluster | cluster: true | { minCount, gap, itemSize } | Same | A cluster is one keyboard stop that opens into its items. |
xss | sanitised by default | Different | Labels and tooltips are text, never HTML. A template is HTML, run through the library's own sanitiser unless templateOptions.sanitize is false. |
| vis-timeline | Temporeum | Status | Notes |
|---|---|---|---|
locale | locale | Same | A BCP 47 tag, through Intl. timeZone sets the zone the axis is drawn in. |
locales | strings | Different | Month and day names come from Intl for any locale. The timeline's own words — its name, the zoom buttons, "New item" — come from a dictionary: strings over DEFAULT_STRINGS (@temporeum/timeline-core/intl). |
format | formatTick(tick, timeZone) | Different | One function for every tick label; tick.major tells the major ones apart. |
timeAxis: { scale, step } | — | Not yet | Ticks are chosen automatically for the zoom level. |
showMajorLabels, showMinorLabels | — | Not yet | formatTick can return an empty string for the labels to hide. |
showCurrentTime | decorations: [{ kind: "now", time }] | Different | Drawn where you put it; update it with timeline.update when it should move. |
addCustomTime(time, id) | timeBars: [{ id, time }] | Different | A time bar is a slider: drag it, or Tab to it and press an arrow. A move is timeBarMoved — vis's timechanged — and timeline.timeBars is where the reader left them. |
rtl | direction: "rtl" | Renamed | Time runs from right to left, the lane labels and the keyboard's arrows follow. "auto" takes the container's dir. |
| vis-timeline | Temporeum | Status | Notes |
|---|---|---|---|
item.id | id | Different | A string. Convert numeric ids with String(id). |
item.content | label | Renamed | Plain text. A label that does not fit is shortened with an ellipsis, up to 1 000 items per screen width. |
item.start, item.end | start, end | Different | A Date or epoch milliseconds — not a string. Convert with new Date(value). |
item.group | laneId | Renamed | |
item.className | className | Same | |
item.style | — | Not yet | Use className and a stylesheet; the theme's custom properties style the rest. |
item.title | tooltip: (item) => text | Different | Shown on hover and on keyboard focus. The item's accessible name stays its label, the dates and the lane; hooks.describeItem changes that. |
item.editable: false | movable: false, resizable: false | Different | Moving and resizing are separate. An item's editable: true on a timeline that is not editable turns editing on in fromVis, with every other item held still, as vis-timeline lets an item opt in. |
item.selectable | selectable | Same | |
item.type: "point" | kind: "point" | Renamed | A dot at its start with a line to the axis. An item without end and without a kind is an instant drawn as a thin bar. |
item.type: "background" | kind: "background" | Renamed | A labelled band behind every lane; nothing can hit it. A background bound to one group's rows is not here yet. |
item.type: "box", type option | kind: "box" | Renamed | The item's bar with a line to the axis. Set it per item; there is no timeline-wide default type. |
item.subgroup, stackSubgroups | — | Not yet | Rows inside a group's rows are not supported; nest a group instead (groups with parentId). |
| vis-timeline | Temporeum | Status | Notes |
|---|---|---|---|
groups | groups (or lanes) | Renamed | An array of { id, label, parentId? }; a group's content is its label, and an item names its group with laneId. Each group gets a header row you can collapse. Flat lanes without headers still work. |
setGroups(groups) | timeline.update({ groups }) | Renamed | |
groupOrder | the order of `groups` | Different | Sort the array. A reader can reorder the groups too; that comes out as a groupMoved. |
nestedGroups, showNested | groups: [{ id, parentId }] | Different | A group names its parent instead of its children. Collapsing is a click on the header, Enter, or timeline.toggleGroup(id); a group starts collapsed with collapsed: true. |
groupEditable (drag to reorder) | drag a group header, or Alt + arrow | Different | Always on; the reorder is reported as a reversible groupMoved and the library does not save it. |
groupTemplate | — | Not yet | Lane labels are text. |
| vis-timeline | Temporeum | Status | Notes |
|---|---|---|---|
editable: true | editing: { enabled: true } | Renamed | Move and resize, with a key for every gesture. Every edit is a reversible command. |
editable: { updateGroup: true } | editing: { crossLane: true } | Renamed | |
editable: { add, remove }, onAdd, onRemove | editing: { add, remove, createItem, canAdd, canRemove } | Renamed | A double click on empty row space or n adds; the remove handle on a selected item or Delete removes. canAdd and canRemove are synchronous vetoes; createItem makes the new item. |
snap | editing: { snapMs, snapOrigin } | Different | A fixed grid instead of a function. A grid of whole days lies on the timeline's timeZone midnights, all year. |
onMoving, onMove | editing.canMove(item, next) | Different | A synchronous yes or no for every step of a drag. The finished edit arrives as itemMoved with its command. |
onUpdate | on("itemDblClick") | Different | Open your own editor on a double click. |
multiselect | editing: { multiSelect: true } | Renamed | |
selectable: false | selectable: false on each item | Different |
| vis-timeline | Temporeum | Status | Notes |
|---|---|---|---|
setSelection(ids) | timeline.select(ids) | Renamed | Or own it: selectedIds in the configuration. |
getSelection() | timeline.getSnapshot().selectedIds | Renamed | |
focus(id) | select([id]) + scrollTo(start) | Different |
| vis-timeline | Temporeum | Status | Notes |
|---|---|---|---|
on("select") | on("selectionChanged") | Renamed | { ids, previousIds }. |
on("rangechange"), on("rangechanged") | on("viewportChanged") | Renamed | { viewport, reason }; viewport.domain is the window. |
on("click") on an item | on("itemClick") | Renamed | |
on("doubleClick") | on("itemDblClick") | Renamed | |
on("itemover"), on("itemout") | on("itemHover") | Renamed | One event; its payload says which item, or none. |
on("changed") after an edit | on("itemMoved"), on("itemResized"), on("command") | Different | command carries every edit as a reversible command — the undo stack's input. |
on("timechange"), addCustomTime | timeBars, on("timeBarMoved") | Renamed | A time bar is a slider: dragged, or moved with the arrows when it has the focus. The event says where it went, and where it was. |
on("contextmenu") | — | Not yet | Listen for contextmenu on the container. |
What is not here yet
The "Not yet" rows above, in short:
- rows inside a group's rows (vis-timeline's
subgroup), and a background bound to one group; - the axis on top, item alignment, a custom time axis, hiding the major or minor labels;
- inline item styles, group templates and context-menu events.
Nested groups, item types and adding and removing from the timeline are plugins; so are hidden dates, draggable time bars, rolling mode, tooltips (time tools), and right to left and a dictionary for the timeline's words (internationalisation). Live changes without a reload are in the base, and fromVis converts a setup in one call. If your app depends on one of the rest, say so in an issue: the order is decided by what users need.