Gantt

pro

Gantt plots tasks, milestones, and dependencies across a timeline, with drag-to-reschedule, resize-to-adjust-duration, and drag-to-set-progress built in.

Installation

Add this component with the NateUI CLI.

npx nateui@latest add Gantt

Examples

Every example below shares the same chart interactions: hold Space and drag to pan, hover a row to highlight it in both the table and the chart, and scrollbars stay hidden until the pointer is over the chart.

View Modes

Off Days

Grouped Tasks

Add & Edit Tasks

Resizable Bars

Resizable Task List

Custom Columns

Custom Bar Content

Bar Context Menu

Fixed Height

API

Gantt

PropDescriptionTypeDefault
tasksRows to render: tasks, milestones, and project groups. Required.Task<T>[]—
columnsTask-list columns shown on the left. An empty array renders the default Name column; null hides the task list and shows the timeline only.Columns<T>[] | null[]
customBarContentRenders custom content inside each task bar's inset label area. Called for task-type rows only. Not the route for a menu — use renderBarMenu.CustomBarContent<T>—
renderBarMenuMenu content for a bar's right-click menu. Return Dropdown.Item children. Applies to tasks, milestones, and project rows.(task: Task<T>) => ReactNode—
viewModeTime scale of the timeline.ViewMode'Day'
viewDateScrolls the timeline so this date is in view.Date—
scrollToTodayOnLoadScrolls to today on first render when today falls inside the data range. Ignored when viewDate is set.booleantrue
refImperative handle for scrolling the timeline. See GanttRef.Ref<GanttRef>—
preStepsCountEmpty time columns kept before the first task.number1
localeLocale used for the header's day and month labels.string'en-GB'
rtlLays the chart out right-to-left.booleanfalse
classNameExtra classes on the Gantt wrapper element.string—
rowHeightHeight of each row, in px.number50
gridColumnsWidthWidth of each time column, in px.number100
headerHeightHeight of the date header, in px.number70
ganttHeightFixed chart height in px; rows scroll vertically inside it. 0 grows to fit every row.number0
resizableTaskListLets the user drag the divider between the task list and the timeline.booleantrue
defaultTaskListWidthInitial task-list width in px. Omit to size to the columns' content.number—
taskListMinWidthLower clamp when dragging the task list, in px.number160
taskListMaxWidthUpper clamp when dragging the task list, in px.number720
resizableColumnsLets the user drag the boundary between task-list columns.booleantrue
barFillPercentage of the row height a bar occupies.number60
barCornerRadiusCorner radius of bars, in px.number4
handleWidthWidth of the move/resize handles, in px.number8
barProgressClassSVG fill-* class for a task bar's progress fill.string'fill-primary'
barWrapperClassSVG fill-* class for a task bar's track.string'fill-[#b8c2cc]'
projectProgressClassProgress fill for project rows.string'fill-primary'
projectWrapperClassTrack fill for project rows.string'fill-[#b8c2cc]'
milestoneClassFill for milestone diamonds.string'fill-primary'
todayColorFill color of the current-day column.string'var(--nui-muted)'
offDaysWeekday indices shaded as non-working, 0 = Sunday. Only applies to Day and finer view modes. [] disables.number[][0, 6]
offDayClassSVG fill-* class for non-working columns. Non-working columns show a diagonal hatch by default; passing this replaces the hatch with a flat wash.string''
rollUpParentsDraws project rows spanning their children instead of their own dates. Dates only — a project's own progress stays as authored. Computed for display; never written to your data.booleanfalse
arrowClassExtra class on dependency arrows, which otherwise inherit border.string''
arrowIndentHorizontal indent of dependency arrows, in px.number20
timeStepSnap interval for drag and resize, in ms.number300000
TooltipContentComponent rendered in the hover/drag tooltip.FC<{ task: Task<T> }>StandardTooltipContent
pushDependentsMoves a dragged task's dependents with it — live during the drag, committed on drop. Only pushes later, never pulls earlier.booleanfalse
onDateChangeFires after a bar is moved or resized. Return false to reject the change — with pushDependents on, this reverts every task that moved, not just the dragged one. Providing this also enables the move and resize handles. The second argument is the task's direct successors when pushDependents is off (informational only); every task that actually moved when it's on.(task: Task<T>, children: Task<T>[]) => void | boolean | Promise<void | boolean>—
onProgressChangeFires after the progress handle is dragged. Return false to reject. Providing this also enables the progress handle.(task: Task<T>, children: Task<T>[]) => void | boolean | Promise<void | boolean>—
onExpanderClickFires when a project's expander is toggled. Providing this also hides the children of collapsed projects.(task: Task<T>) => void—
onClickFires when a bar is clicked.(task: Task<T>) => void—
onDoubleClickFires when a bar is double-clicked.(task: Task<T>) => void—
onSelectFires when a bar gains or loses selection (focus).(task: Task<T>, isSelected: boolean) => void—
onDeleteFires when Delete is pressed on a focused bar. Return truthy to remove it.(task: Task<T>) => void | boolean | Promise<void | boolean>—
onCellClickFires when an empty timeline cell is clicked. Receives the row's task and the clicked column's start date. Providing this also enables the cell hover highlight and pointer cursor.(task: Task<T>, date: Date, event: MouseEvent) => void—
onBarContextMenuFires on right-click of a bar, before the menu opens.(task: Task<T>, event: MouseEvent) => void—

The move and resize handles only appear when onDateChange is set, and the progress handle only appears when onProgressChange is set. The chart keeps its own internal copy of the rows, so a bar stays where it is dragged even before the callback updates your data; return false from the callback to snap it back. With custom columns, the built-in expander button is not rendered — wire the cell's expander node to your own handler when you need clickable groups.

Types

type ViewMode =
  | 'Hour'
  | 'QuarterDay'
  | 'Half Day'
  | 'Day'
  | 'Week'
  | 'Month'
  | 'QuarterYear'
  | 'Year';
 
type TaskType = 'task' | 'milestone' | 'project';
 
type Task<T = object> = {
  id: string;
  type: TaskType;
  name: string;
  start: Date;
  end: Date;
  progress: number; // 0-100
  dependencies?: string[]; // ids of tasks this row follows
  project?: string; // parent project id
  hideChildren?: boolean; // collapse a project's children
  displayOrder?: number; // row sort order
  isDisabled?: boolean; // lock a row from editing
  styles?: { progressClass?: string; wrapperClass?: string };
} & T;
 
type Columns<T = object> = {
  header: string | ReactNode;
  cell: (task: Task<T> & { expander: ReactNode }) => string | ReactNode;
  width?: string | number;
};
 
type CustomBarContent<T = object> = (task: BarTask<T>) => ReactNode;
 
// BarTask is a Task extended with its computed bar geometry.
type BarTask<T = object> = Task<T> & {
  index: number;
  x1: number;
  x2: number;
  y: number;
  height: number;
  progressX: number;
  progressWidth: number;
  barChildren: BarTask<T>[];
};
 
type GanttRef = {
  today: () => void; // scroll today's column to the left edge (no-op if out of range)
  goTo: (date: Date) => void; // scroll the given date's column to the left edge (no-op if out of range)
  next: () => void; // scroll forward one viewport width
  prev: () => void; // scroll back one viewport width
  scrollToTask: (taskId: string) => void; // scroll the given task's start into view (no-op if unknown)
};