Migrating from frappe-gantt
A frappe-gantt chart moves over by turning each task into an item in a lane of its own, its dependencies string into a list of arrows, and its progress from a percentage into a fraction. This page maps every frappe-gantt option, task 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: frappe-gantt tasks, converted
Loading…
Run “frappe-gantt tasks, 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 class="gantt" style="display: grid; grid-template-columns: 240px 1fr; height: 300px"> <div id="grid"></div> <div id="timeline" style="height: 300px"></div> </div> <p class="log" id="log" aria-live="polite">Drag a bar, or press Alt and an arrow on the focused one.</p>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 npm install --save-dev vite npx viteOpen the address it prints,
http://localhost:5173/.
The plugins
import { createTimeline, registerPlugin } from "@temporeum/timeline-element";
import { ganttPlugin, mountTaskGrid } from "@temporeum/timeline-element/gantt";
import { toolsPlugin } from "@temporeum/timeline-element/tools";
// The Gantt chart, and the zoom presets for frappe-gantt's view modes.
registerPlugin(ganttPlugin, toolsPlugin);From the running example example/migration-frappe-gantt
frappe-gantt is all chart; here the chart is a plugin, and so are some of the tools a frappe-gantt setup maps to. The Gantt plugin draws the arrows, the progress, the baselines and milestones. The time tools plugin, toolsPlugin from @temporeum/timeline-element/tools, is what the zoom presets for the view modes (zoomPresets), the popup (tooltip), ignored weekends (hiddenDates) and a click on a date (rangeSelect) need. Register both once, before the first timeline; a configuration that needs a plugin that is not registered throws a TypeError where the timeline is created, naming the import that is missing.
What changes
- A task is an item in a lane. frappe-gantt draws one task per row; give each task a lane with the same id, and the lanes are the rows. With
ganttand the Gantt plugin (registerPlugin(ganttPlugin), from@temporeum/timeline-element/gantt), the timeline draws the arrows, the progress fill, the baseline and milestones (Gantt);mountTaskGrid, from the same entry point, draws the table beside it. - Times are
Dateobjects or epoch milliseconds. frappe-gantt reads"2026-03-02"as local midnight, and a date-onlyendas the end of that day.new Date("2026-03-02")is UTC midnight, so convert the way the example does: local midnight, and a day added to a date-only end. - Progress is 0 to 1, not 0 to 100.
- Dependencies are one list for the chart,
{ from, to, type, lagMs }, not a string on each task. The type is finish-to-start unless it says otherwise. - Read-only until told. frappe-gantt is editable until
readonly; a Temporeum timeline is read-only untilediting.enabled. A move moves one task: the scheduler of@temporeum/timeline-core/schedulesays where the ones after it go (scheduling). - Views are zooms. Day, week, month and year are the default
zoomPresets(withtoolsPluginregistered); the axis follows the zoom instead of being redrawn per mode. - The keyboard works by default. Every gesture has a key, the arrows follow the dependencies with
]and[, and screen readers see the chart as a listbox (accessibility).
The mapping
The chart in each row's example has five tasks with progress, each in its own lane, joined by a chain of dependencies; the row's code is the only difference.
| frappe-gantt | Temporeum | Status | Notes |
|---|---|---|---|
new Gantt("#gantt", tasks, options) | createTimeline(container, { items, lanes, gantt: { dependencies } }) | Renamed | A task is an item in a lane of its own: the lanes are the chart's rows. gantt turns on the arrows, the progress fill and milestones; mountTaskGrid draws the table beside it. |
gantt.refresh(tasks) | timeline.setItems(items) | Renamed | Hand the timeline the next array; it redraws once. |
gantt.update_task(id, details) | timeline.updateItems([{ id, ...changes }]) | Renamed | Several at once in one call; undefined takes a field away. |
gantt.update_options(options) | timeline.update(config) | Renamed | A key given as undefined goes back to its default; a key left out keeps its value. |
container_height | height | Renamed | In CSS pixels. Without one, the container's own height. |
language: "fr" | locale: "fr" | Renamed | Month and day names come from Intl for any locale; the timeline's own words from strings (@temporeum/timeline-core/intl). |
| frappe-gantt | Temporeum | Status | Notes |
|---|---|---|---|
view_mode: "Week", gantt.change_view_mode("Week") | zoomPresets: true, timeline.zoomToPreset("week") | Different | Day, week, month and year are the default presets. A preset zooms the view to that span; the columns follow the zoom instead of being redrawn per mode. |
view_mode_select: true | zoomPresets: true | Different | The presets are buttons over the timeline. |
scroll_to: "start" | "end" | date | domain: [start, end] | Different | The first view. Without a domain the view fits the tasks, with a margin. |
gantt.scroll_current() | timeline.scrollTo(Date.now()) | Renamed | The library never reads the clock; the host passes the time. |
today_button: true | a button that calls timeline.scrollTo(Date.now()) | Different | Not built in; one line of the host's. |
infinite_padding: true | limits: { min, max } (unbounded by default) | Different | The view pans without end unless limits bounds it. |
ignore: ["weekend"], is_weekend | hiddenDates: [{ start, end, repeat: "weekly" }] | Different | A hidden stretch is cut out of the axis; with repeat it is a pattern — here every weekend. |
holidays | decorations: [{ kind: "era", id, start, end, label }] | Different | A named stretch drawn behind the tasks, with its label. A one-day holiday is a day long. |
date_format, view_modes[].upper_text / lower_text | formatTick(tick, timeZone) | Different | One function for every axis label; tick.granularity says what the label stands for. |
bar_height, padding | density | Different | Row height follows the density; auto picks one at which every row fits. |
auto_move_label: true | on by default | Same | A label stays on the visible part of a bar the edge cuts off; one that does not fit inside its bar is shortened with an ellipsis, or given up with labelOverflow: "drop". |
popup, popup_on: "hover" | tooltip: (item) => text | Renamed | On hover and on keyboard focus. The text is the item's; the library adds no HTML of its own. |
show_expected_progress | — | Not yet | baseline draws the planned span under the bar, which shows a slip; an expected-progress mark is not drawn. |
arrow_curve | — | Not yet | Arrows are routed around the bars with right angles. |
lines: "vertical" | "horizontal" | "none" | — | Not yet | The grid follows the axis ticks and the lanes; it is styled, not switched off by an option. |
upper_header_height, lower_header_height | — | Not yet | The axis is one band of its own height. |
| frappe-gantt | Temporeum | Status | Notes |
|---|---|---|---|
readonly: true | the default (editing: { enabled: false }) | Different | A Temporeum timeline is read-only until editing.enabled; frappe-gantt is editable until readonly. |
readonly_dates: true | item.movable: false, item.resizable: false | Different | Per item, with editing on. |
snap_at: "1d" | editing: { snapMs: DAY } | Renamed | In milliseconds, from the epoch or snapOrigin. A grid of whole days lies on the timeline's timeZone midnights, all year. |
move_dependencies: true | scheduleTasks(items, dependencies) from /schedule | Different | A move moves one task. The scheduler of @temporeum/timeline-core/schedule says where the ones after it go — with lags, constraints and working calendars — and setItems puts them there. |
| frappe-gantt | Temporeum | Status | Notes |
|---|---|---|---|
task.id | item.id, and the lane's id | Same | One lane per task, with the same id, keeps one task per row. |
task.name | item.label | Renamed | Also the item's accessible name. |
task.start, task.end: "YYYY-MM-DD" | item.start, item.end: Date or epoch ms | Different | frappe-gantt reads a date string as local midnight, and a date-only end as the end of that day. new Date(2026, 2, 6) is local midnight; new Date("2026-03-06") is UTC midnight. For an inclusive end, add a day. |
task.progress: 0 to 100 | item.progress: 0 to 1 | Different | Divide by 100. Drawn as the filled part of the bar. |
task.dependencies: "a, b" | gantt: { dependencies: [{ from: "a", to: id }] } | Different | One list for the whole chart, each with its type (FS by default) and lag. |
task.custom_class | item.className | Renamed | |
task.color, task.color_progress | item.className and a stylesheet | Different | Colours come from the theme's custom properties and the item's class. |
task.duration: "3d" | item.end | Different | Give the end: start plus the duration. |
task.description | tooltip | Different | The tooltip reads it from the item, or from data. |
a zero-length task | item.kind: "milestone" | Different | A diamond at its start, with the gantt option. |
task.thumbnail | — | Not yet | No picture on the bar itself; a template draws sanitised HTML in its place. |
| frappe-gantt | Temporeum | Status | Notes |
|---|---|---|---|
on_click(task) | on("itemClick") | Renamed | Also fires for Enter on the focused item. |
on_double_click(task) | on("itemDblClick") | Renamed | |
on_hover(task) | on("itemHover") | Renamed | item is null when the pointer leaves every item. |
on_date_change(task, start, end) | on("itemMoved"), on("itemResized") | Different | Each carries the item, where it was, where it is now, and the reversible command. The library never saves. |
on_view_change(mode) | on("viewportChanged") | Renamed | Fires for every change of the view, with its reason. |
on_date_click(date) | rangeSelect: true, on("rangeSelected") | Different | A drag on empty time selects a range; a click on it selects nothing. |
on_progress_change(task, progress) | — | Not yet | Progress is drawn, not dragged: change it with updateItems. |
What is not here yet
The "Not yet" rows above, in short: dragging progress (and its event), expected progress, curved arrows, switching the grid lines off, header heights, and a picture on the bar. If your chart depends on one of them, say so in an issue: the order is decided by what users need.