Gantt React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React Gantt chart: project tasks as bars you drag and resize, with progress, dependency arrows, milestones, groups and a phone task list.
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.
Gantt is a Pro React component in the React data display components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add gantt, together with Avatar, Badge, Button, Date Picker, Empty State and 13 more and the npm packages date-fns, @tanstack/react-virtual, motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add ganttGantt is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import Gantt from @/components/ui/gantt.
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
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.
titledefault""ReactNodeA heading at the start of the toolbar, like the project's name.
defaultZoomdefault"week""day" | "week" | "month"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) => voidFires when the zoom control, Fit or the API changes the zoom.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"The default bar color; a task's own tone or color wins.
colordefault""stringAny CSS color for the bars; replaces the tone. Leave empty to use the tone.
radiusdefault"md""none" | "sm" | "md" | "lg"Corner radius of the panel: 0 / 12 / 16 / 24px.
rowHeightdefault44numberRow height in px. Bars stay 28px tall and keep a 44px touch target.
listWidthdefault320numberWidth 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).
maxHeightdefault520numberHeight in px after which the body scrolls, with sticky headers and a sticky task list. 0 grows with the rows.
showListdefaulttruebooleanThe task list beside the timeline. Off, group bars carry their names.
toolbardefaulttruebooleanThe row with the title, the zoom, Today and Fit.
listColumnsdefault"["assignee", "start", "progress"]"("assignee" | "start" | "end" | "duration" | "progress" | "status")[]Columns after the name, shown from 1024px of width.
editabledefaulttruebooleanBars move and resize by drag, by Alt+arrows and from the details. Off, the details only show the task.
progressEditabledefaultfalsebooleanA handle under each bar to drag its progress, in steps of 5%.
snapdefault"day""day" | "week"What a drag, a resize and Alt+arrows snap to.
showDependenciesdefaulttruebooleanArrows from each predecessor's end to its task's start; dashed red when the task starts too early.
showTodaydefaulttruebooleanToday's line across the timeline, its day in the header and the Today button.
showWeekendsdefaulttruebooleanShades the locale's weekend days in the day and week zooms.
loadingdefaultfalsebooleanSkeleton rows in place of the tasks.
skeletonCountdefault6numberHow many skeleton rows show while loading.
responsivedefaulttruebooleanTask details open in a bottom sheet below 768px instead of a popover.
localedefault""stringDates, 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) => voidReplaces the built-in details popover and sheet.
columnWidthdefault"{ day: 40, week: 20, month: 6 }"Partial<Record<GanttZoom, number>>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.
weekStartsOndefault-10 | 1 | 2 | 3 | 4 | 5 | 6 | -1First 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).
groupsExpandedstring[]Controlled ids of the open groups; pair it with onGroupsExpandedChange.
defaultGroupsExpandedstring[]Open groups while uncontrolled. Default: every group.
onGroupsExpandedChange(ids: string[]) => voidFires when a group folds or unfolds.
emptyStateReactNodeShown when there are no tasks. Default: an EmptyState with labels.empty ("No tasks yet").
nowDate | numberWhere today's line sits. Default: the current time, updated every minute.
setApi(api: GanttApi) => voidReceives { scrollToToday(), scrollToTask(id), setZoom(zoom), fit() } after mount.
toolbarActionsReactNodeExtra buttons at the end of the toolbar.
labelsPartial<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.
classNamesPartial<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
constantThe English strings of the chart (column headers, zoom buttons, announcements, the details dialog). Spread it and override a few to translate. Type: GanttLabels.
computeSummary
functionThe 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
functionLists 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
functionOrders 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
typeOne 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
typeThe handle setApi receives: scrollToToday(), scrollToTask(id), setZoom(zoom) and fit().
GanttViolation
type{ task: GanttTask; predecessor: GanttTask }, one broken finish-to-start dependency.
GanttLabels
typeThe 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.
Dependencies
Files
- components/ui/gantt.tsx
FAQ
Is the React Gantt component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Gantt?
Run npx wingo-ui@latest add gantt. It also adds the Wingo UI items it builds on (Avatar, Badge, Button, Date Picker, Empty State and 13 more) and the npm packages date-fns, @tanstack/react-virtual, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/gantt.
How does Gantt behave on phones?
Task details open in a bottom sheet below 768px instead of a popover. The responsive prop controls this and is on by default.
See also
More React data display components
Articles using this component
- React Gantt Chart: Drag, Progress and Dependencies
Component guides10 min read