Gantt
Work on a timeline: drag a bar to reschedule and its edges to change the start or end, with a computed critical path and dependency conflicts surfaced as you create them.
src/components/ui/gantt.tsxAbout
From the component's own source, where it was written.
Work on a timeline: bars for duration, dragged to reschedule.
**The view Kanban, Timeline and Stepper cannot give you.** Kanban shows state and hides time. A timeline shows moments, not spans. A stepper shows order, not overlap. Only this shows *how long* and *at the same time as what* — which is the entire question in scheduling.
**A read-only Gantt is a picture of a plan, and plans change.** Dragging is not decoration here: the whole reason to look at this chart is to notice that something has slipped, and the useful next action is to move it. A bar is dragged to reschedule, and its edges are dragged to change the start or the end independently.
**Everything snaps, and the snap is the unit the plan is kept in.** Dates dragged to arbitrary millisecond boundaries produce a plan nobody can read back; snapMinutes defaults to a day, which is the granularity most plans are actually managed at.
**Dragging is not the only way to move a task.** A bar is a real button: arrow keys move it a snap at a time, shift-arrow changes its end, and both announce through aria-valuetext. A chart that can only be edited with a pointer cannot be edited at all by a good number of people.
**It is monochrome, and meaning is carried by shape rather than hue.** A plan is read for structure — what is long, what overlaps, what is late — and a palette assigning arbitrary colours to tasks adds a legend to learn without adding information. It also means the two states that matter are not encoded in colour alone, which is the usual accessibility failure: the critical path is ringed and a dependency conflict is outlined in dashes, both legible to anyone who cannot separate red from grey.
**The critical path is computed, not annotated.** Highlighting it by hand is how a Gantt goes stale: someone slips a task, nobody re-marks the path, and the chart now points at the wrong risk. Dependency conflicts — a task starting before what it depends on ends — are surfaced as you create them, because an impossible schedule should look impossible while you are making it, not after you save.
Install
npx astralyx-ui add gantt
Usage
import { Gantt } from '@/components/ui/gantt'
<Gantt tasks={tasks} onChange={setTasks} criticalPath today={new Date()} />Composer
Change the props and the source below follows.
Gantt chart
4 dependency conflicts: a task starts before what it depends on finishes.
Drag a bar to reschedule, its edges to change the start or end. Focus one and use the arrow keys — shift-arrow changes the end.
<Gantt
tasks={tasks}
criticalPath={true}
today={new Date()}
/>Examples
A release plan, critical path marked
<Gantt tasks={plan} onChange={setPlan} criticalPath today={new Date()} />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
Gantt
Extends Omit<ComponentProps<'div'>, 'onSelect' | 'onChange'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| tasks | GanttTask[] | required | { id, label, start, end, progress?, dependsOn?, milestone? }. |
| onChange | (tasks: GanttTask[]) => void | — | Supplying either makes the chart editable; omitting both makes it read-only. A read-only Gantt is a printout of a plan, and the reason to look at one is usually that something needs moving. |
| onTaskChange | (task: GanttTask, previous: GanttTask) => void | — | Supplying either makes the chart editable; omitting both makes it read-only. A read-only Gantt is a printout of a plan, and the reason to look at one is usually that something needs moving. |
| onSelect | (task: GanttTask) => void | — | — |
| selectedId | string | — | — |
| snapMinutes | number | 1440 | A day. Dragging to arbitrary millisecond boundaries produces a plan nobody can read back. |
| criticalPath | boolean | true | The longest dependency chain is derived from the data. Marking it by hand is how a Gantt goes stale — someone slips a task, nobody re-marks the path, and it points at the wrong risk. |
| labelWidth | number | 160 | Column width for the task names. |
| rowHeight | number | 34 | — |
| dateFormat | (date: Date) => string | DEFAULT_DATE_FORMAT | — |
| ticks | number | 5 | Number of ticks along the top. |
| today | Date | — | A vertical marker. Omitted by default rather than assumed. |
| emptyLabel | string | 'Nothing scheduled.' | — |
| label | string | 'Gantt chart' | — |
Types
GanttTask
Work on a timeline: bars for duration, dragged to reschedule.
| Field | Type | Description |
|---|---|---|
| id | string | — |
| label | ReactNode | — |
| start | Date | string | — |
| end | Date | string | — |
| progress? | number | 0–1. Drawn as a fill inside the bar. |
| dependsOn? | string[] | Ids this task cannot start before. |
| group? | string | — |
| milestone? | boolean | — |
| locked? | boolean | Excluded from dragging and from keyboard editing. |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| vs Kanban / Timeline / Stepper | the missing view | Kanban shows state and hides time. A timeline shows moments, not spans. A stepper shows order, not overlap. Only this shows how long, and at the same time as what. |
| dragging | body and edges | The body moves the task and keeps its duration; the edges change the start and the end independently. Handles are 8px, which is the difference between resizing and moving by accident. |
| keyboard | arrows and shift-arrow | Arrows move by one snap, shift-arrow changes the end, and the dates are announced through aria-valuetext. A chart editable only by pointer is not editable at all for a good number of people. |
| conflicts | live | Recomputed against the drag rather than the saved data, so an impossible schedule looks impossible while you are making it. |
| monochrome | shape, not hue | A plan is read for structure — what is long, what overlaps, what is late — and arbitrary per-task colour adds a legend without adding information. The critical path is ringed and a conflict is outlined in dashes, so neither state is carried by colour alone. |