Basic chart
A chart with four tasks, three links and two event handlers, made with
@ganttlane/dom and, in React, with
@ganttlane/react. The buttons above the chart call methods
(setViewMode, scrollToTask) without recreating it.
pnpm add @ganttlane/dom # vanillapnpm add @ganttlane/react react react-dom # Reactnpm install @ganttlane/dom # vanillanpm install @ganttlane/react react react-dom # Reactyarn add @ganttlane/dom # vanillayarn add @ganttlane/react react react-dom # Reactbun add @ganttlane/dom # vanillabun add @ganttlane/react react react-dom # Reactimport type { Link, Task } from '@ganttlane/core';
// 2026-03-02 is a Monday. A task with an `end` ends on that day (inclusive); a task with a// `duration` lasts that many days.export const tasks: Task[] = [ { id: 'design', name: 'Design', start: '2026-03-02', end: '2026-03-06', progress: 60 }, { id: 'build', name: 'Build', start: '2026-03-09', end: '2026-03-20', progress: 10 }, { id: 'qa', name: 'QA', start: '2026-03-23', duration: 5 }, { id: 'launch', name: 'Launch', type: 'milestone', start: '2026-03-30' },];
export const links: Link[] = [ { source: 'design', target: 'build' }, // finish-to-start, the default type { source: 'build', target: 'qa' }, { source: 'qa', target: 'launch' },];import type { GanttOptions } from '@ganttlane/dom';
export const options = { // The scale of the timeline: 'hour', 'quarter-day', 'half-day', 'day', 'week' (here), 'month' // or 'year'. It can change later without recreating the chart. viewMode: 'week', // Open on the plan. The default is 'today', which for a plan in the past or the future is the // nearest end of its range. scroll: { initial: 'first-task' },} satisfies Partial<GanttOptions>;import '@ganttlane/dom/styles.css';
import { createGantt } from '@ganttlane/dom';
import { links, tasks } from './data';import { options } from './options';
const VIEW_MODES = ['day', 'week', 'month'] as const;
export function mount(container: HTMLElement): () => void { const root = document.createElement('div'); const toolbar = document.createElement('div'); toolbar.setAttribute('role', 'toolbar'); // The chart fills its container and scrolls inside it, so the container needs a height. const chart = document.createElement('div'); chart.style.height = '380px'; const status = document.createElement('p'); status.textContent = 'Click a bar, or drag it, its edges or the handle at its foot.'; root.append(toolbar, chart, status); container.append(root);
const gantt = createGantt(chart, { tasks, links, ...options, onTaskClick: (record) => { status.textContent = `Clicked ${record.name}`; }, // One event per drag, with every task it moved (the dragged one and what follows it). onTasksChange: (changeSet) => { const moved = changeSet.changes.map((change) => String(change.id)).join(', '); status.textContent = `${changeSet.reason}: moved ${moved}`; }, });
// Everything can change later without recreating the chart. const controller = new AbortController(); const addButton = (label: string, onClick: () => void): void => { const button = document.createElement('button'); button.type = 'button'; button.textContent = label; button.addEventListener('click', onClick, { signal: controller.signal }); toolbar.append(button); }; for (const id of VIEW_MODES) addButton(id, () => gantt.setViewMode(id)); addButton('Go to QA', () => gantt.scrollToTask('qa'));
// When the page is done with the chart: removes every node, listener and timer it added. return () => { controller.abort(); gantt.destroy(); root.remove(); };}import '@ganttlane/react/styles.css';
import { Gantt, type GanttHandle } from '@ganttlane/react';import { useRef, useState } from 'react';
import { links, tasks } from './data';import { options } from './options';
const VIEW_MODES = ['day', 'week', 'month'];
export default function Example() { const chart = useRef<GanttHandle>(null); // `viewMode` is controlled by its prop: the chart shows the one your state holds. const [viewMode, setViewMode] = useState<string>(options.viewMode); const [status, setStatus] = useState( 'Click a bar, or drag it, its edges or the handle at its foot.', );
return ( <> <div role="toolbar"> {VIEW_MODES.map((id) => ( <button key={id} type="button" onClick={() => setViewMode(id)}> {id} </button> ))} {/* The ref is the chart's methods. */} <button type="button" onClick={() => chart.current?.scrollToTask('qa')}> Go to QA </button> </div> <Gantt ref={chart} // The chart fills its container and scrolls inside it, so the container needs a height. style={{ height: 380 }} // The chart owns the data (uncontrolled): these are where it starts. defaultTasks={tasks} defaultLinks={links} {...options} viewMode={viewMode} onTaskClick={(record) => { setStatus(`Clicked ${record.name}`); }} // One event per drag, with every task it moved (the dragged one and what follows it). onTasksChange={(changeSet) => { const moved = changeSet.changes.map((change) => String(change.id)).join(', '); setStatus(`${changeSet.reason}: moved ${moved}`); }} /> <p>{status}</p> </> );}Files
data.ts: the tasks and the links.options.ts: the options the example is about.vanilla.ts:createGantt, the handlers, the buttons and the clean-up.react.tsx:<Gantt>with the same data and options.
Options
| Option | Value | What it does |
|---|---|---|
viewMode |
'week' |
The scale of the timeline: columns are weeks |
scroll.initial |
'first-task' |
Opens on the plan; the default, 'today', is far from these dates |
onTaskClick |
a function (in the example) | Called with the TaskRecord of a bar that was clicked, never after a drag |
onTasksChange |
a function (in the example) | One ChangeSet per drag, with every task it moved (the dragged task and the tasks that follow it) |
What to try
Drag a bar to move it: its successors follow along the links. Drag its edges to resize it, and the
little handle at the foot of a bar to change its progress. Drag from the round handle beside a bar
onto another bar to link them, click an arrow to open its menu (remove the dependency), and hover a
bar for its tooltip. Escape cancels a drag, and holding Alt while dragging snaps to the data
step instead of the view mode’s.
Notes
- The container needs a height. The chart fills it and scrolls inside it. Give it one with CSS
(
<div id="chart" style="height: 380px">), withstyleorclassNameon<Gantt>, or give the chart its own withlayout.height. - Import the stylesheet once:
@ganttlane/dom/styles.css, or@ganttlane/react/styles.cssin a React app (it is a copy, so an app that installed only@ganttlane/reactcan import it). - Clean up.
destroy()removes every node, listener and timer the chart added, and is safe to call twice.<Gantt>does it when it unmounts. - React: controlled and uncontrolled props.
defaultTasksanddefaultLinksare where the chart starts, and then it owns the data.viewModeis the other kind: the chart shows the one you pass, so the buttons here change it through state. Object props such asscroll={{ initial: 'first-task' }}may be written inline: they are compared by content. - React: the methods. Give the component a
ref(useRef<GanttHandle>(null), thenref.current?.scrollToTask('qa')). To keep the data in your own state instead, see Controlled data.