Quick Start
Render a Gantt chart in about 15 lines. This is the exact code in
examples/plain-html-demo/src/main.ts
— it also appears verbatim in the project README, and a CI check keeps all three copies in
sync.
The whole thing
import { createGantt, toTaskId } from '@fluxgantt/core';
import { withRender } from '@fluxgantt/core/render';
import { withInteraction } from '@fluxgantt/core/interaction';
// Rendering and interaction are opt-in mixins — a headless consumer (server-side
// scheduling, tests) never downloads them. Compose both for a live, editable chart.
const gantt = withInteraction(
withRender(
createGantt({
tasks: [
{ id: toTaskId('design'), name: 'Design', start: '2026-08-03', end: '2026-08-05', progress: 1, type: 'task' },
{ id: toTaskId('build'), name: 'Build', start: '2026-08-05', end: '2026-08-10', progress: 0.6, type: 'task' },
{ id: toTaskId('review'), name: 'Review', start: '2026-08-10', end: '2026-08-12', progress: 0, type: 'task' },
{ id: toTaskId('launch'), name: 'Launch', start: '2026-08-12', end: '2026-08-12', progress: 0, type: 'milestone' },
{ id: toTaskId('docs-task'), name: 'Write docs', start: '2026-08-06', end: '2026-08-11', progress: 0.2, type: 'task' },
],
dependencies: [
{ from: toTaskId('design'), to: toTaskId('build'), type: 'FS' },
{ from: toTaskId('build'), to: toTaskId('review'), type: 'FS' },
],
}),
),
);
gantt.on('task:moved', (task, prevStart) => {
console.log(`${task.name} moved from ${prevStart}`);
});
gantt.mount(document.getElementById('gantt')!);You need a container element in the page for mount() to render into:
<div id="gantt" style="height: 420px; overflow: auto"></div>In a browser without native Temporal, also install
@js-temporal/polyfill and run this once, before the code above. The example app does it in
src/temporal.ts; see Installation:
import { Temporal } from '@js-temporal/polyfill';
// Once, before the first createGantt(). A no-op where Temporal is native.
(globalThis as unknown as { Temporal?: typeof Temporal }).Temporal ??= Temporal;That's it — you get a rendered chart with draggable bars, a computed critical path (outlined in
dashed red), and a task:moved event firing whenever a bar is dragged.
Line by line
createGantt(config)builds a headlessGanttInstance. Nothing is rendered yet — you can already callgantt.getTasks()organtt.computeCriticalPath()in Node or a test with no DOM.withRender(...)andwithInteraction(...)are opt-in capability mixins on their own subpaths: the first addsmount()/unmount()/refresh(), the second wires the drag, click-select and keyboard gestures into every mount. Each returns the same instance, so the order does not matter — but apply them before the firstmount(). Skip them and you get a purely headless engine, and your bundle never includes the renderer. (Import/export lives behind a third,withIo.)toTaskId('design')brands a plain string as aTaskId. IDs are branded types so you can't accidentally pass aDependencyIdwhere aTaskIdis expected.from/toon a dependency areTaskIds too, hencetoTaskId(...)there as well.typeandprogressare required on every task input (typeis one oftask/summary/milestone/project;progressis0..1).start/endaccept an ISO date string, aDate, or a Temporal value.type: 'FS'is Finish-to-Start — the default. FluxGantt also supportsSS,FF,SF, plus alagin hours. See Dependencies.gantt.on('task:moved', …)subscribes to an event; it returns an unsubscribe function.gantt.mount(el)(fromwithRender) attaches the SVG renderer and wires drag interactions (fromwithInteraction, unlessreadOnlyis set).
Where to go next
- Tasks — the full
Task/TaskInputshape. - Dependencies — FS/SS/FF/SF, lag, cycle rejection.
- Critical Path —
computeCriticalPath()and its result. - Drag & Drop — what each gesture does and the events it emits.
- React / Vue — the framework wrappers.