Vesture

Data

GanttNew in 0.7.0

A project timeline: a collapsible task tree beside day-scale bars, with progress fills, milestones, finish-to-start dependency arrows, and optional drag / keyboard editing.

Gantt

Preview

Zoom
TaskDoneStart
Feb 2026
Mar 2026
Apr 2026
28
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Planning
–2026-03-02
Scope
100%2026-03-02
Scope · 100%
Spec
60%2026-03-09
Spec · 60%
Build
–2026-03-16
API
30%2026-03-16
API · 30%
UI
10%2026-03-19
UI · 10%
QA
–2026-04-06
QA
Release
–2026-04-13

Drag a bar, drag its edges, or focus it and press ←/→ (Shift+←/→ resizes).

Usage

tsx
import { useState } from "react";
import { Gantt, type GanttTask } from "@vesture/react";

const [tasks, setTasks] = useState<GanttTask[]>([
  { id: "plan", name: "Planning", start: "2026-03-02", end: "2026-03-13" },
  { id: "scope", name: "Scope", start: "2026-03-02", end: "2026-03-06", parentId: "plan", progress: 100 },
  { id: "build", name: "Build", start: "2026-03-16", end: "2026-04-03", dependsOn: ["plan"] },
  { id: "ship", name: "Release", start: "2026-04-06", end: "2026-04-06", type: "milestone", dependsOn: ["build"] },
]);

<Gantt
  tasks={tasks}
  onTaskChange={(id, change) =>
    setTasks((ts) => ts.map((t) => (t.id === id ? { ...t, ...change } : t)))
  }
  isTaskEditable={(t) => (t.progress ?? 0) < 100}
  columns={[{ key: "done", header: "Done", width: 60, render: (t) => `${t.progress ?? 0}%` }]}
/>

Behavior

  • Read-only until you pass onTaskChange. With it, bars move by dragging the body and resize by dragging either edge; focused bars also move with ←/→ and resize their end with Shift+←/→. Gantt never mutates your tasks — it reports { start, end } and you apply it (the demo does exactly that).
  • A task that has children (via parentId) becomes a summary bar spanning them, with an expand/collapse toggle. Summary bars are never directly editable — edit the children instead.
  • Dates are ISO strings (YYYY-MM-DD), end inclusive. Internally they are whole UTC days, so daylight-saving changes never shift a bar by a day.
  • dependsOn draws elbow arrows from the end of each predecessor to the start of the task. Arrows follow live while you drag, and are hidden when either end is inside a collapsed group.
  • type: "milestone" renders a diamond on the task's start day; it can be moved but not resized.
  • snapDays makes drags and arrow keys move in multiples (7 = whole weeks). isTaskEditable locks individual tasks even when editing is on — for example completed work.
  • Task colors resolve task.color → getBarColor(task) → the theme primary, so a status palette is one callback.
  • columns adds fields beside the task name (owner, % done, dates…); range pins the visible dates; scale="week" thins the day labels for long projects; weekendDays defines non-working days.

Props

GanttTask

PropTypeDefaultDescription
idstring—Required.
namestring—Required.
startstring—Required. ISO date, YYYY-MM-DD.
endstring—Required. ISO date, inclusive — a one-day task has start === end.
progressnumber—0–100; fills the bar from the left.
parentIdstring—Nests under another task. Unknown ids are treated as top-level.
dependsOnstring[]—Ids of tasks that must finish first; drawn as arrows.
type"task" | "milestone""task"Milestones render as a diamond on start.
colorstring—Any CSS color for this task's bar.

Data & editing

PropTypeDefaultDescription
tasksGanttTask[]—Required.
onTaskChange(taskId: string, change: { start: string; end: string }) => void—Passing this turns on drag and keyboard editing.
isTaskEditable(task: GanttTask) => boolean—Per-task gate on top of onTaskChange.
snapDaysnumber1Whole days a drag or Arrow key moves by.
onTaskClick(task: GanttTask) => void—Click or Enter on a bar (not fired after a drag).

Appearance

PropTypeDefaultDescription
dayWidthnumber32Pixel width of one day — the zoom level.
rowHeightnumber36
listWidthnumber240Width of the task-name column.
columns{ key: string; header: ReactNode; width: number; render: (task) => ReactNode }[]—Extra columns beside the name.
nameHeaderReactNode"Task"Header of the name column.
range{ start: string; end: string }—Forces the visible date range instead of fitting the tasks.
scale"day" | "week""day"Week labels only the first day of each week.
weekendDaysnumber[][0, 6]Shaded non-working days (Date#getUTCDay numbers). [] for none.
showTodaybooleantrueThe today marker.
showDependenciesbooleantrueDependency arrows.
todaystring—Pins the 'today' date (ISO) — useful for demos and tests.
defaultCollapsedstring[]—Ids of summary tasks collapsed on first render.
getBarColor(task) => string | undefined—Color per task when task.color is unset.
renderBarLabel(task) => ReactNode—Text inside bars; return null for none.