# Gantt

> A React Gantt chart: project tasks as bars you drag and resize, with progress, dependency arrows, milestones, groups and a phone task list.

- Type: React component (Data Display)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add gantt`
- Docs: https://wingo-ui.com/components/gantt

## Interaction

Drag a bar to move it and drag its edges to resize it; it snaps to whole days (or weeks) on release, a pill above it shows the new dates and the original stays behind faded until you let go. Near the timeline's edge the view scrolls along. With progressEditable a small handle under the bar sets the progress. Click or tap a bar, or a name in the task list, for its details: dates, progress and Save, in a popover on a desktop and a bottom sheet on a phone. Arrows join dependent tasks and turn dashed red when a task starts before its predecessor ends. Chevrons fold groups; drag the task list's edge to widen it. The zoom switches between days, weeks and months and keeps the date in the middle in place; Today and Fit scroll for you. On touch, press and hold a bar to pick it up. Keyboard: Tab lands on one task; Up / Down, Page Up / Page Down, Home / End move between tasks; Enter opens the details; Alt+Left / Right moves a task by one snap step, Alt+Shift+Left / Right changes its end; Left / Right fold and unfold a group; t scrolls to today; Escape cancels a drag. Below 512px of width the chart becomes a list of tasks with mini bars.

## Installation

```bash
npx wingo-ui@latest add gantt
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/gantt` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/gantt.tsx`

npm dependencies: `npm i date-fns @tanstack/react-virtual motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Badge](https://wingo-ui.com/components/badge) ([Markdown](https://wingo-ui.com/components/badge.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Date Picker](https://wingo-ui.com/components/date-picker) ([Markdown](https://wingo-ui.com/components/date-picker.md))
- [Empty State](https://wingo-ui.com/components/empty-state) ([Markdown](https://wingo-ui.com/components/empty-state.md))
- [Popover](https://wingo-ui.com/components/popover) ([Markdown](https://wingo-ui.com/components/popover.md))
- [Progress](https://wingo-ui.com/components/progress) ([Markdown](https://wingo-ui.com/components/progress.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Slider](https://wingo-ui.com/components/slider) ([Markdown](https://wingo-ui.com/components/slider.md))
- [Tooltip](https://wingo-ui.com/components/tooltip) ([Markdown](https://wingo-ui.com/components/tooltip.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useLocale](https://wingo-ui.com/components/use-locale) ([Markdown](https://wingo-ui.com/components/use-locale.md))
- [useLongPress](https://wingo-ui.com/components/use-long-press) ([Markdown](https://wingo-ui.com/components/use-long-press.md))
- [Date utils](https://wingo-ui.com/components/date-utils) ([Markdown](https://wingo-ui.com/components/date-utils.md))
- [Relative time](https://wingo-ui.com/components/relative-time) ([Markdown](https://wingo-ui.com/components/relative-time.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { useState } from "react"
import { Gantt, type GanttTask } from "@/components/ui/gantt"
// also exported: computeSummary(tasks) (span and weighted progress of every parent),
// findDependencyViolations(tasks) (tasks starting before a predecessor ends), topoSortTasks(tasks) (predecessors first)

const START: GanttTask[] = [
  { id: "analysis", name: "Analysis", start: new Date(2026, 8, 22), end: new Date(2026, 9, 3) },
  {
    id: "workshop",
    name: "Requirements workshop",
    parentId: "analysis",
    start: new Date(2026, 8, 22),
    end: new Date(2026, 8, 25),
    progress: 1,
    assignee: { name: "Liam Walker" },
  },
  {
    id: "specs",
    name: "Specifications",
    parentId: "analysis",
    start: new Date(2026, 8, 28),
    end: new Date(2026, 9, 3),
    progress: 0.4,
    dependencies: ["workshop"],
  },
  { id: "go-live", name: "Go-live", start: new Date(2026, 10, 3), end: new Date(2026, 10, 3), milestone: true, dependencies: ["specs"] },
]

export function ProjectPlan() {
  const [tasks, setTasks] = useState(START)
  return (
    <Gantt
      title="Acme CRM rollout"
      tasks={tasks}
      locale="en-US"
      onTaskChange={(task, change) =>
        setTasks((list) => list.map((t) => (t.id === task.id ? { ...t, ...change } : t)))
      }
      labels={{ fit: "Fit all", week: (n) => `Week ${n}` }}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `tasks` | `GanttTask[]` |  | { id, name, start, end (inclusive), progress? 0 to 1, parentId?, dependencies? (finish to start), milestone?, assignee? { name, avatar? }, tone?, color?, status? { label, tone? }, readOnly? }. Order is display order within each parent; a task with children is a group whose span and progress are computed. |
| `title?` | `ReactNode` | `""` | A heading at the start of the toolbar, like the project's name. |
| `defaultZoom?` | `"day" \| "week" \| "month"` | `"week"` | Starting zoom while uncontrolled: day is 40px a day with day headers, week 20px a day with week numbers, month 6px a day with month names. Pair zoom with onZoomChange to control it. |
| `zoom?` | `"day" \| "week" \| "month"` |  | The controlled zoom; the toolbar's segmented control and Fit call onZoomChange. |
| `onZoomChange?` | `(zoom: GanttZoom) => void` |  | Fires when the zoom control, Fit or the API changes the zoom. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The default bar color; a task's own tone or color wins. |
| `color?` | `string` | `""` | Any CSS color for the bars; replaces the tone. Leave empty to use the tone. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg"` | `"md"` | Corner radius of the panel: 0 / 12 / 16 / 24px. |
| `rowHeight?` | `number` | `44` | Row height in px. Bars stay 28px tall and keep a 44px touch target. |
| `listWidth?` | `number` | `320` | Width of the task list in px (at least 200). People can drag its edge, or focus the edge and use the arrow keys. From 512 to 1023px the list keeps only the names, at most 220px (and 40% of the width). |
| `maxHeight?` | `number` | `520` | Height in px after which the body scrolls, with sticky headers and a sticky task list. 0 grows with the rows. |
| `showList?` | `boolean` | `true` | The task list beside the timeline. Off, group bars carry their names. |
| `toolbar?` | `boolean` | `true` | The row with the title, the zoom, Today and Fit. |
| `listColumns?` | `("assignee" \| "start" \| "end" \| "duration" \| "progress" \| "status")[]` | `"[\"assignee\", \"start\", \"progress\"]"` | Columns after the name, shown from 1024px of width. |
| `editable?` | `boolean` | `true` | Bars move and resize by drag, by Alt+arrows and from the details. Off, the details only show the task. |
| `progressEditable?` | `boolean` | `false` | A handle under each bar to drag its progress, in steps of 5%. |
| `snap?` | `"day" \| "week"` | `"day"` | What a drag, a resize and Alt+arrows snap to. |
| `showDependencies?` | `boolean` | `true` | Arrows from each predecessor's end to its task's start; dashed red when the task starts too early. |
| `showToday?` | `boolean` | `true` | Today's line across the timeline, its day in the header and the Today button. |
| `showWeekends?` | `boolean` | `true` | Shades the locale's weekend days in the day and week zooms. |
| `loading?` | `boolean` | `false` | Skeleton rows in place of the tasks. |
| `skeletonCount?` | `number` | `6` | How many skeleton rows show while loading. |
| `responsive?` | `boolean` | `true` | Task details open in a bottom sheet below 768px instead of a popover. |
| `locale?` | `string` | `""` | Dates, week numbers, month and weekday names, the first day of the week and the weekend. Empty follows the app. |
| `onTaskChange?` | `(task: GanttTask, change: GanttTaskChange) => (void \| false \| Promise<unknown>)` |  | A move, a resize, a progress drag or a details save, with { start, end, progress? }. The bar keeps the new place optimistically; return false or reject to spring it back. Dependents are not shifted for you. |
| `onTaskClick?` | `(task: GanttTask) => void` |  | Replaces the built-in details popover and sheet. |
| `columnWidth?` | `Partial<Record<GanttZoom, number>>` | `"{ day: 40, week: 20, month: 6 }"` | Pixels per day for each zoom. |
| `range?` | `{ start: Date; end: Date }` |  | The visible dates; it grows to include every task. Default: the tasks' span plus 7 days on each side. |
| `weekStartsOn?` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| -1` | `-1` | First day of a week column and of the date pickers: 0 Sunday, 1 Monday ... 6 Saturday; -1 follows the locale (Monday in en-GB, Sunday in en-US). |
| `groupsExpanded?` | `string[]` |  | Controlled ids of the open groups; pair it with onGroupsExpandedChange. |
| `defaultGroupsExpanded?` | `string[]` |  | Open groups while uncontrolled. Default: every group. |
| `onGroupsExpandedChange?` | `(ids: string[]) => void` |  | Fires when a group folds or unfolds. |
| `emptyState?` | `ReactNode` |  | Shown when there are no tasks. Default: an EmptyState with labels.empty ("No tasks yet"). |
| `now?` | `Date \| number` |  | Where today's line sits. Default: the current time, updated every minute. |
| `setApi?` | `(api: GanttApi) => void` |  | Receives { scrollToToday(), scrollToTask(id), setZoom(zoom), fit() } after mount. |
| `toolbarActions?` | `ReactNode` |  | Extra buttons at the end of the toolbar. |
| `labels?` | `Partial<GanttLabels>` |  | Every string, English by default (DEFAULT_GANTT_LABELS): task, assignee, start, end, date, duration(days, formatted), durationColumn, progress, status, today, fit, zoom, zoomDay, zoomWeek, zoomMonth, week(n), range(start, end), project(range), completed(percent), assignedTo(name), moved(task, range), resized(task, range), progressChanged(task, percent), violation(task, predecessor), failed(task), milestone, group(count, formatted), details, save, cancel, close, expand, collapse, resizeList, timeline, noAssignee, empty, emptyDescription, loading. |
| `classNames?` | `Partial<Record<GanttSlot, string>>` |  | Extra classes for toolbar, title, body, listHeader, listRow, timelineHeader, grid, row, bar, barProgress, barLabel, milestone, summary, dependencies, today, details, mobile and mobileRow. |

## Parts

### DEFAULT_GANTT_LABELS (constant)

The English strings of the chart (column headers, zoom buttons, announcements, the details dialog). Spread it and override a few to translate. Type: GanttLabels.

### computeSummary (function)

The span and duration-weighted progress of every group task, computed from its children, the same numbers the chart draws. Use it for a project header or a report. Signature: (tasks: GanttTask[]) => Map<string, { start: Date; end: Date; progress: number }>.

### findDependencyViolations (function)

Lists the tasks that start on or before their predecessor's last day (finish to start), for a warning banner or a save check. Signature: (tasks: GanttTask[]) => GanttViolation[].

### topoSortTasks (function)

Orders tasks so every predecessor comes before the tasks that depend on it; a cycle keeps the remaining tasks in their original order. Signature: (tasks: GanttTask[]) => GanttTask[].

### GanttTask (type)

One row: id, name, start, end (inclusive), progress 0-1, parentId for groups, dependencies, milestone, assignee, tone or color, status and readOnly.

### GanttTaskChange (type)

{ start: Date; end: Date; progress?: number }, what onTaskChange receives after a drag, a resize or a progress edit.

### GanttApi (type)

The handle setApi receives: scrollToToday(), scrollToTask(id), setZoom(zoom) and fit().

### GanttViolation (type)

{ task: GanttTask; predecessor: GanttTask }, one broken finish-to-start dependency.

### GanttLabels (type)

The shape of DEFAULT_GANTT_LABELS, the type of the labels prop.

## Examples

- **Month zoom**: A six month marketing plan with groups, a milestone and brand tones.
- **Dependency violation**: Client data import starts before Specifications ends: a dashed red arrow, a warning in the list and an announcement.
- **Editable progress**: progressEditable adds a handle under each bar; progress snaps to 5%.
- **Read-only status view**: editable={false} with status badges in the list, for reports.
- **200 tasks**: Generated construction sites; rows virtualize above 150 visible tasks.
- **Phone**: The list with mini bars and the details sheet, at 390px.

## See also

- [Scheduler](https://wingo-ui.com/components/scheduler) ([Markdown](https://wingo-ui.com/components/scheduler.md))
- [Calendar View](https://wingo-ui.com/components/calendar-view) ([Markdown](https://wingo-ui.com/components/calendar-view.md))
- [Timeline](https://wingo-ui.com/components/timeline) ([Markdown](https://wingo-ui.com/components/timeline.md))
- [Kanban](https://wingo-ui.com/components/kanban) ([Markdown](https://wingo-ui.com/components/kanban.md))
- [Date Picker](https://wingo-ui.com/components/date-picker) ([Markdown](https://wingo-ui.com/components/date-picker.md))

---

Canonical: https://wingo-ui.com/components/gantt
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
