mantine-gantt

A gantt chart component for mantine

Installation

yarn add mantine-gantt

Usage

Task Name
Start
End
Duration
Project Planning
Feb 1, 2026
Feb 5, 2026
5d
Requirements Analysis
Feb 3, 2026
Feb 9, 2026
7d
UI/UX Design
Feb 8, 2026
Feb 17, 2026
10d
Database Schema Design
Feb 10, 2026
Feb 14, 2026
5d
API Development
Feb 15, 2026
Feb 28, 2026
14d
Frontend Development
Feb 18, 2026
Mar 7, 2026
18d
Integration Testing
Mar 5, 2026
Mar 11, 2026
7d
User Acceptance Testing
Mar 12, 2026
Mar 16, 2026
5d
Documentation
Feb 20, 2026
Mar 11, 2026
20d
Deployment & Launch
Mar 17, 2026
Mar 19, 2026
3d
January 2026
February 2026
March 2026
25
26
27
28
29
30
31
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
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
Project Planning
Requirements Analysis
UI/UX Design
Database Schema Design
API Development
Frontend Development
Integration Testing
User Acceptance Testing
Documentation
Deployment & Launch
import { Gantt, type GanttTask } from 'mantine-gantt';

const mockTasks: GanttTask[] =  [
  {
    id: '1',
    label: 'Project Planning',
    startDate: '2026-02-01',
    duration: 5,
    progress: 100,
    dependencies: [],
  },
  {
    id: '2',
    label: 'Requirements Analysis',
    startDate: '2026-02-03',
    duration: 7,
    progress: 80,
    dependencies: ['1'],
  },
  {
    id: '3',
    label: 'UI/UX Design',
    startDate: '2026-02-08',
    duration: 10,
    progress: 60,
    dependencies: ['2'],
    color: 'violet',
  },
  {
    id: '4',
    label: 'Database Schema Design',
    startDate: '2026-02-10',
    duration: 5,
    progress: 40,
    dependencies: ['2'],
    color: 'teal',
  },
  {
    id: '5',
    label: 'API Development',
    startDate: '2026-02-15',
    duration: 14,
    progress: 20,
    dependencies: ['4'],
    color: 'orange',
  },
  {
    id: '6',
    label: 'Frontend Development',
    startDate: '2026-02-18',
    duration: 18,
    progress: 10,
    dependencies: ['3'],
  },
  {
    id: '7',
    label: 'Integration Testing',
    startDate: '2026-03-05',
    duration: 7,
    progress: 0,
    dependencies: ['5', '6'],
    color: 'red',
  },
  {
    id: '8',
    label: 'User Acceptance Testing',
    startDate: '2026-03-12',
    duration: 5,
    progress: 0,
    dependencies: ['7'],
    color: 'pink',
  },
  {
    id: '9',
    label: 'Documentation',
    startDate: '2026-02-20',
    duration: 20,
    progress: 15,
    dependencies: ['2'],
    color: 'gray',
  },
  {
    id: '10',
    label: 'Deployment & Launch',
    startDate: '2026-03-17',
    duration: 3,
    progress: 0,
    dependencies: ['8'],
    color: 'green',
  },
];

function Demo() {
  return <Gantt defaultTasks={mockTasks} />;
}

Custom columns & click-to-open modal

Pass columns to customize the left panel (the default columns are Task Name, Start, End and Duration). Use onTaskClick to react to clicks - here it opens a modal with task details.

Task
Owner
Duration
Project Planning
Ada
5d
UI/UX Design
Grace
10d
API Development
Linus
14d
January 2026
February 2026
March 2026
25
26
27
28
29
30
31
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
1
2
3
4
5
6
7
8
Project Planning
UI/UX Design
API Development
import { useState } from 'react';
import { Gantt, type GanttColumn, type GanttTask } from 'mantine-gantt';
import { Badge, Modal, Stack, Text } from '@mantine/core';

const tasks: Array<GanttTask & { owner: string }> = [
  { id: '1', label: 'Project Planning', startDate: '2026-02-01', duration: 5, progress: 100, owner: 'Ada' },
  { id: '2', label: 'UI/UX Design', startDate: '2026-02-06', duration: 10, progress: 60, owner: 'Grace', dependencies: ['1'], color: 'violet' },
  { id: '3', label: 'API Development', startDate: '2026-02-16', duration: 14, progress: 20, owner: 'Linus', dependencies: ['2'], color: 'orange' },
];

// Custom left-hand columns: Duration is built in, plus an Owner column of our own.
const columns: Array<GanttColumn> = [
  { header: 'Task', render: (t) => t.label },
  { header: 'Owner', render: (t) => (t as any).owner, width: 90 },
  { header: 'Duration', render: (t) => `${t.duration}d`, width: 80 },
];

function Demo() {
  const [selected, setSelected] = useState<GanttTask | null>(null);
  return (
    <>
      <Gantt defaultTasks={tasks} columns={columns} onTaskClick={setSelected} />
      <Modal opened={!!selected} onClose={() => setSelected(null)} title={selected?.label} centered>
        {selected && (
          <Stack gap="xs">
            <Text size="sm">Starts {selected.startDate} · {selected.duration} days</Text>
            <Badge>{selected.progress}% complete</Badge>
          </Stack>
        )}
      </Modal>
    </>
  );
}

Grouping tasks

Set parentId on a task and it becomes a child row. A task that has children renders as a summary bar: it spans its whole subtree and shows a duration-weighted average progress, so the dates you give a parent are only a placeholder. Nesting goes as deep as you need. Rows are collapsible via the chevron - defaultExpandedIds picks which parents start open (omit it and everything is open), and onToggleExpand reports each toggle.

Task Name
Start
End
Duration
Design
Feb 2, 2026
Feb 11, 2026
10d
Wireframes
Feb 2, 2026
Feb 5, 2026
4d
Visual design
Feb 6, 2026
Feb 11, 2026
6d
Build
Feb 12, 2026
Feb 20, 2026
9d
API
Feb 12, 2026
Feb 19, 2026
8d
UI
Feb 16, 2026
Feb 20, 2026
5d
Launch
Feb 26, 2026
Feb 27, 2026
2d
January 2026
February 2026
March 2026
26
27
28
29
30
31
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
1
2
3
4
5
6
Design
Wireframes
Visual design
Build
API
UI
Launch
import { Gantt, type GanttTask } from 'mantine-gantt';

// A task with a parentId becomes a child row; its parent renders as a summary bar
// spanning the whole subtree, with a duration-weighted progress. Nesting is unlimited.
const tasks: GanttTask[] = [
  { id: 'design', label: 'Design', startDate: '2026-02-02', duration: 1, progress: 0 },
  { id: 'wireframes', label: 'Wireframes', startDate: '2026-02-02', duration: 4, progress: 100, parentId: 'design' },
  { id: 'visuals', label: 'Visual design', startDate: '2026-02-06', duration: 6, progress: 60, parentId: 'design', color: 'violet' },
  { id: 'build', label: 'Build', startDate: '2026-02-12', duration: 1, progress: 0 },
  { id: 'api', label: 'API', startDate: '2026-02-12', duration: 8, progress: 40, parentId: 'build', dependencies: ['wireframes'], color: 'orange' },
  { id: 'ui', label: 'UI', startDate: '2026-02-16', duration: 10, progress: 10, parentId: 'build', dependencies: ['visuals'] },
  { id: 'auth', label: 'Auth screens', startDate: '2026-02-16', duration: 5, progress: 0, parentId: 'ui', color: 'teal' },
  { id: 'launch', label: 'Launch', startDate: '2026-02-26', duration: 2, progress: 0, dependencies: ['ui'], color: 'green' },
];

function Demo() {
  return (
    <Gantt
      defaultTasks={tasks}
      // Omit to start with everything expanded. Listed ids stay open, the rest collapse.
      defaultExpandedIds={['design', 'build']}
      onToggleExpand={(id, expanded) => console.log(id, expanded)}
    />
  );
}

View modes

viewMode switches the timeline scale between day (default), week and month. It shrinks the day column derived from columnWidth rather than changing the underlying day grid, so drags still snap to whole days. weekStart controls the header's week numbering and the grid separators.

locale (default 'en') sets the language of month names and dates - pass 'de', or navigator.language to follow the browser.

Task Name
Start
End
Duration
Discovery
Feb 2, 2026
Feb 11, 2026
10d
Implementation
Feb 12, 2026
Mar 7, 2026
24d
Rollout
Mar 9, 2026
Mar 20, 2026
12d
January 2026
February 2026
March 2026
Week 5
Week 6
Week 7
Week 8
Week 9
Week 10
Week 11
Week 12
Week 13
Discovery
Implementation
Rollout
import { useState } from 'react';
import { Gantt, type GanttProps, type GanttTask } from 'mantine-gantt';
import { SegmentedControl, Stack } from '@mantine/core';

const tasks: GanttTask[] = [
  { id: '1', label: 'Discovery', startDate: '2026-02-02', duration: 10, progress: 100 },
  { id: '2', label: 'Implementation', startDate: '2026-02-12', duration: 24, progress: 45, dependencies: ['1'], color: 'orange' },
  { id: '3', label: 'Rollout', startDate: '2026-03-09', duration: 12, progress: 0, dependencies: ['2'], color: 'green' },
];

function Demo() {
  const [viewMode, setViewMode] = useState<GanttProps['viewMode']>('week');

  // 'week' and 'month' shrink the day column (columnWidth / 2 and / 6) so long
  // projects fit without horizontal scrolling. weekStart drives the header numbering.
  return (
    <Stack>
      <SegmentedControl
        value={viewMode}
        onChange={(value) => setViewMode(value as GanttProps['viewMode'])}
        data={['day', 'week', 'month']}
      />
      <Gantt defaultTasks={tasks} viewMode={viewMode} weekStart={1} />
    </Stack>
  );
}

Navigation, zoom and selection

  • scrollTo - 'today', a Date, or { taskId } scrolls the timeline there on mount and whenever the value changes (a task also brings its row into view).
  • Ctrl+wheel over the timeline zooms the day column between 8px and 200px around the cursor; onColumnWidthChange reports the new width. Holding the middle mouse button autoscrolls the timeline in the direction you move, on every platform.
  • isNonWorkingDay replaces the default Saturday/Sunday shading with your own predicate (holidays, company calendar). It only affects the shading - durations stay in calendar days.
  • selectedTaskId marks a task's bar and list row with data-selected; pair it with onTaskClick to build a selection.
  • While dragging, the bar shows its snapped start/end above the cursor (showDragLabel={false} turns it off); Escape cancels the drag.

Column width: 40px (Ctrl+wheel to zoom, middle button to autoscroll)

Task Name
Start
End
Duration
Discovery
Feb 2, 2026
Feb 11, 2026
10d
Implementation
Feb 12, 2026
Mar 7, 2026
24d
Rollout
Mar 9, 2026
Mar 20, 2026
12d
January 2026
February 2026
March 2026
26
27
28
29
30
31
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
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
Discovery
Implementation
Rollout
import { useState } from 'react';
import { Gantt, type GanttTask } from 'mantine-gantt';
import { Button, Group, Stack, Text } from '@mantine/core';

const tasks: GanttTask[] = [
  { id: '1', label: 'Discovery', startDate: '2026-02-02', duration: 10, progress: 100 },
  { id: '2', label: 'Implementation', startDate: '2026-02-12', duration: 24, progress: 45, dependencies: ['1'], color: 'orange' },
  { id: '3', label: 'Rollout', startDate: '2026-03-09', duration: 12, progress: 0, dependencies: ['2'], color: 'green' },
];

// Fridays are off, plus one public holiday.
const isNonWorkingDay = (d: Date) =>
  d.getDay() === 0 || d.getDay() === 5 || d.getDay() === 6 || d.toDateString() === 'Mon Feb 16 2026';

function Demo() {
  const [scrollTo, setScrollTo] = useState<{ taskId: string } | Date>(new Date(2026, 1, 2));
  const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
  const [columnWidth, setColumnWidth] = useState(40);

  return (
    <Stack>
      <Group>
        {tasks.map((t) => (
          <Button key={t.id} size="xs" variant="light" onClick={() => setScrollTo({ taskId: t.id })}>
            Go to {t.label}
          </Button>
        ))}
        <Text size="sm">Column width: {columnWidth}px (Ctrl+wheel to zoom, middle button to autoscroll)</Text>
      </Group>
      <Gantt
        defaultTasks={tasks}
        scrollTo={scrollTo}
        selectedTaskId={selectedTaskId}
        onTaskClick={(task) => setSelectedTaskId((id) => (id === task.id ? null : task.id))}
        isNonWorkingDay={isNonWorkingDay}
        columnWidth={columnWidth}
        onColumnWidthChange={setColumnWidth}
      />
    </Stack>
  );
}

Phones and narrow containers

  • The button in the task list header collapses the panel to a narrow strip, giving the timeline the full width (task names stay readable on the bars). While the chart is narrower than the task list plus 240px - a phone in portrait - the panel starts collapsed and follows the container width until the user toggles it. defaultTaskListCollapsed={false} opts out; taskListCollapsed + onTaskListCollapsedChange make it controlled.
  • On touch screens a swipe always pans the timeline, even when it starts on a bar. To move, resize or link a task, press and hold the bar (or a handle) for 300ms, then drag.

Critical path & baselines

highlightCriticalPath runs CPM over the dependency graph and paints the zero-slack tasks and links with criticalPathColor. Independently, any task with a baseline gets a thin planned bar underneath the actual one - turn those off with showBaselines={false}.

Task Name
Start
End
Duration
Planning
Feb 2, 2026
Feb 5, 2026
4d
Backend
Feb 6, 2026
Feb 13, 2026
8d
Design
Feb 6, 2026
Feb 8, 2026
3d
Integration
Feb 16, 2026
Feb 20, 2026
5d
January 2026
February 2026
26
27
28
29
30
31
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
Planning
Backend
Design
Integration
import { Gantt, type GanttTask } from 'mantine-gantt';

// baseline is the planned schedule: it renders as a thin bar under the actual one,
// so slipped tasks are visible at a glance.
const tasks: GanttTask[] = [
  { id: '1', label: 'Planning', startDate: '2026-02-02', duration: 4, progress: 100, baseline: { startDate: '2026-02-02', duration: 3 } },
  { id: '2', label: 'Backend', startDate: '2026-02-06', duration: 8, progress: 60, dependencies: ['1'], baseline: { startDate: '2026-02-05', duration: 7 } },
  { id: '3', label: 'Design', startDate: '2026-02-06', duration: 3, progress: 100, dependencies: ['1'], color: 'teal', baseline: { startDate: '2026-02-05', duration: 3 } },
  { id: '4', label: 'Integration', startDate: '2026-02-16', duration: 5, progress: 0, dependencies: ['2', '3'], baseline: { startDate: '2026-02-12', duration: 5 } },
];

function Demo() {
  // highlightCriticalPath runs CPM over the dependency graph; tasks with zero slack
  // (and the links between them) are painted with criticalPathColor.
  return <Gantt defaultTasks={tasks} highlightCriticalPath criticalPathColor="red" showBaselines />;
}

Milestones

Set type: 'milestone' on a task to render it as a diamond instead of a bar. A milestone is a zero-length marker pinned to its start day - it can be dragged and linked like any task, but it cannot be resized and its duration is ignored for rendering. Milestones participate in the critical path calculation and in auto-scheduling like regular tasks.

Task Name
Start
End
Duration
Requirements
Apr 1, 2026
Apr 5, 2026
5d
Design review
Apr 6, 2026
Apr 8, 2026
3d
Design approved
Apr 9, 2026
Apr 9, 2026
0d
Implementation
Apr 10, 2026
Apr 17, 2026
8d
Go live
Apr 18, 2026
Apr 18, 2026
0d
March 2026
April 2026
25
26
27
28
29
30
31
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
Requirements
Design review
Implementation
import { Gantt, type GanttTask } from 'mantine-gantt';

const tasks: GanttTask[] = [
  {
    id: '1',
    label: 'Requirements',
    startDate: '2026-04-01',
    duration: 5,
    progress: 100,
  },
  {
    id: '2',
    label: 'Design review',
    startDate: '2026-04-06',
    duration: 3,
    progress: 60,
    dependencies: ['1'],
    color: 'violet',
  },
  {
    id: 'm1',
    label: 'Design approved',
    startDate: '2026-04-09',
    duration: 0,
    progress: 0,
    type: 'milestone',
    dependencies: ['2'],
    color: 'grape',
  },
  {
    id: '3',
    label: 'Implementation',
    startDate: '2026-04-10',
    duration: 8,
    progress: 10,
    dependencies: ['m1'],
    color: 'teal',
  },
  {
    id: 'm2',
    label: 'Go live',
    startDate: '2026-04-18',
    duration: 0,
    progress: 0,
    type: 'milestone',
    dependencies: ['3'],
    color: 'red',
  },
];

function Demo() {
  return <Gantt defaultTasks={tasks} showTitle />;
}

Auto-scheduling

With autoSchedule, moving or resizing a task pushes every finish-to-start successor so it never starts before its predecessors end. The cascade is transitive (a whole chain shifts together) and picks the latest predecessor when a task has several dependencies. It works with drags, resizes and keyboard nudges alike. Turned off by default.

Task Name
Start
End
Duration
Foundation
May 4, 2026
May 8, 2026
5d
Framing
May 4, 2026
May 9, 2026
6d
Roofing
May 4, 2026
May 7, 2026
4d
Interior
May 4, 2026
May 11, 2026
8d
April 2026
May 2026
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Foundation
Framing
Roofing
Interior
import { Gantt, type GanttTask } from 'mantine-gantt';

const tasks: GanttTask[] = [
  {
    id: '1',
    label: 'Foundation',
    startDate: '2026-05-04',
    duration: 5,
    progress: 100,
  },
  {
    id: '2',
    label: 'Framing',
    startDate: '2026-05-04',
    duration: 6,
    progress: 40,
    dependencies: ['1'],
    color: 'teal',
  },
  {
    id: '3',
    label: 'Roofing',
    startDate: '2026-05-04',
    duration: 4,
    progress: 0,
    dependencies: ['2'],
    color: 'orange',
  },
  {
    id: '4',
    label: 'Interior',
    startDate: '2026-05-04',
    duration: 8,
    progress: 0,
    dependencies: ['3'],
    color: 'violet',
  },
];

// With autoSchedule, moving or resizing a task pushes every finish-to-start
// successor so it never starts before its predecessor ends.
function Demo() {
  return <Gantt defaultTasks={tasks} autoSchedule showTitle />;
}

Dependency types and lag

An entry in dependencies is either a task id or an object. The id is shorthand for a finish-to-start link with no lag, so existing data keeps working.

dependencies: [
  '1', // finish-to-start, no lag
  { taskId: '2', type: 'SS', lag: 2 }, // start 2 days after task 2 starts
  { taskId: '3', type: 'FF', lag: -1 }, // finish 1 day before task 3 finishes
];

type is 'FS' (default), 'SS', 'FF' or 'SF' - predecessor first, S for start and F for finish. lag is whole days, negative for a lead. The arrow attaches to the ends the type names, autoSchedule enforces the constraint and the critical path accounts for it.

Dragging the link connector always creates a finish-to-start link; set other types and lags in your data. Code that reads dependencies can normalize both shapes with normalizeDependency:

import { normalizeDependency } from 'mantine-gantt';

const ids = (task.dependencies ?? []).map(
  (dep) => normalizeDependency(dep).taskId
);

Working days

By default duration counts calendar days and isNonWorkingDay only shades columns. With workingDays, duration, baseline.duration and dependency lag count working days: a 5-day task that starts on Friday ends the next Thursday and its bar stretches across the weekend.

  • A task dropped on a non-working day snaps to the next working day in the direction it moved.
  • Resizing recounts the working days between the bar's new ends.
  • autoSchedule places successors on working days.
  • isNonWorkingDay defines the calendar, so holidays work the same way as weekends.

A custom column gets the computed row as a third argument; row.span is the number of calendar days the bar covers, which is what you need to print an end date:

const endColumn: GanttColumn = {
  header: 'End',
  render: (task, locale, row) =>
    dayjs(task.startDate)
      .add(Math.max(0, row.span - 1), 'day')
      .format('YYYY-MM-DD'),
};
Task Name
Start
End
Duration
Backend
Feb 2, 2026
Feb 6, 2026
5d
Frontend
Feb 4, 2026
Feb 10, 2026
5d
Docs
Feb 9, 2026
Feb 10, 2026
2d
Release
Feb 12, 2026
Feb 12, 2026
1d
January 2026
February 2026
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Backend
Frontend
Docs
Release
import { Gantt, type GanttTask } from 'mantine-gantt';

// February 2026: the 6th is a Friday.
const tasks: GanttTask[] = [
  { id: '1', label: 'Backend', startDate: '2026-02-02', duration: 5, progress: 60 },
  {
    id: '2',
    label: 'Frontend',
    startDate: '2026-02-04',
    duration: 5,
    progress: 30,
    color: 'teal',
    // starts two working days after Backend starts
    dependencies: [{ taskId: '1', type: 'SS', lag: 2 }],
  },
  {
    id: '3',
    label: 'Docs',
    startDate: '2026-02-09',
    duration: 2,
    progress: 0,
    color: 'grape',
    // must finish together with Frontend
    dependencies: [{ taskId: '2', type: 'FF' }],
  },
  {
    id: '4',
    label: 'Release',
    startDate: '2026-02-12',
    duration: 1,
    progress: 0,
    color: 'red',
    // plain id = finish-to-start, no lag
    dependencies: ['2'],
  },
];

function Demo() {
  return <Gantt defaultTasks={tasks} workingDays autoSchedule />;
}

Deleting dependency links

Click a dependency arrow to delete that link. The component removes the source id from the target's dependencies and fires onLinkDelete(fromTaskId, toTaskId, type) alongside onTasksChange, mirroring how link creation works.

Markers

markers draws extra vertical lines next to the today marker: a deadline, a release, a sprint end. Each takes a date (YYYY-MM-DD), an optional label and a color (default orange). A marker outside the timeline is not rendered and does not widen it - set startDate / endDate if it has to be visible.

Task Name
Start
End
Duration
Requirements
Apr 1, 2026
Apr 5, 2026
5d
Implementation
Apr 6, 2026
Apr 13, 2026
8d
March 2026
April 2026
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Code freeze
Release
Requirements
Implementation
import { useState } from 'react';
import { Stack, Switch } from '@mantine/core';
import { Gantt, type GanttTask } from 'mantine-gantt';

const tasks: GanttTask[] = [
  // locked: signed off, nobody drags it any more
  { id: '1', label: 'Requirements', startDate: '2026-04-01', duration: 5, progress: 100, locked: true },
  {
    id: '2',
    label: 'Implementation',
    startDate: '2026-04-06',
    duration: 8,
    progress: 40,
    dependencies: ['1'],
    color: 'teal',
  },
];

function Demo() {
  const [readOnly, setReadOnly] = useState(false);

  return (
    <Stack>
      <Switch
        label="readOnly"
        checked={readOnly}
        onChange={(event) => setReadOnly(event.currentTarget.checked)}
      />
      <Gantt
        defaultTasks={tasks}
        readOnly={readOnly}
        markers={[
          { date: '2026-04-08', label: 'Code freeze' },
          { date: '2026-04-16', label: 'Release', color: 'green' },
        ]}
      />
    </Stack>
  );
}

Read-only and locked tasks

readOnly turns off every edit: drag, resize, linking, link deletion and keyboard nudges. onTaskClick, selection, expand/collapse, scrolling and zoom keep working. To freeze a single task, set locked: true on it. A locked task can still be the target of a new link, and autoSchedule still shifts it, because a successor that starts before its predecessor ends would make the chart lie.

To reject or clamp a single change instead, use controlled mode and decide in onTasksChange:

<Gantt
  tasks={tasks}
  onTasksChange={(next) => {
    // no task may start before the project kickoff
    if (next.every((task) => task.startDate >= '2026-04-01')) {
      setTasks(next);
    }
  }}
/>

Controlled and uncontrolled

Pass defaultTasks and the component owns the task list (uncontrolled) - this is what the demos above do. Pass tasks together with onTasksChange and your own state is the single source of truth (controlled): the component keeps no copy, so a dragged bar only moves once the new tasks prop arrives. Use this mode to drive the chart from a query cache, a store, or an undo/redo stack.

const [tasks, setTasks] = useState(initialTasks);

<Gantt tasks={tasks} onTasksChange={setTasks} />;

onTaskUpdate, onLinkCreate and onLinkDelete keep working in both modes - they fire alongside onTasksChange, not instead of it.

Migrating from 0.2.x: <Gantt tasks={x} /> becomes <Gantt defaultTasks={x} />.