A flexible, customizable Gantt chart component for React applications with drag-and-drop task scheduling, dark mode support, progress tracking, and multiple view modes.
- Features
- Installation
- Quick Start
- Components
- Task & TaskGroup Data Structure
- View Modes
- Interactive Progress Editing
- Infinite Scroll
- Performance Optimization
- Customization
- Event Handling
- Dark Mode
- Advanced Examples
- Browser Support
- FAQ
- Contributing
- License
- π Interactive timeline with drag-and-drop task scheduling
- π¨ Fully customizable with CSS variables and custom classes
- π Multiple view modes (Minute, Hour, Day, Week, Month, Quarter, Year)
- π Dark mode support built-in
- π± Responsive design that works across devices
- π Progress tracking with visual indicators and interactive updates
- π Task dependencies and relationship management
- π― Event handling for clicks, updates, selections
- π§© Composable API with extensive custom render props for advanced customization
- π Smooth animations with configurable speeds and thresholds
- π Auto-scrolling during drag operations
- β‘ Performance optimized for large timelines (Minute view limited to 500 intervals)
- π Infinite scroll with automatic timeline extension (optional)
npm install react-modern-gantt
yarn add react-modern-gantt
import React, { useState } from 'react';
import GanttChart from 'react-modern-gantt';
// β οΈ IMPORTANT: Don't forget to import the styles!
import 'react-modern-gantt/dist/index.css';
function App() {
const [tasks, setTasks] = useState([
{
id: 'team-1',
name: 'Engineering',
description: 'Development Team',
tasks: [
{
id: 'task-1',
name: 'Website Redesign',
startDate: new Date(2023, 0, 1),
endDate: new Date(2023, 2, 15),
color: '#3b82f6',
percent: 75,
},
// More tasks...
],
},
// More groups...
]);
const handleTaskUpdate = (groupId, updatedTask) => {
setTasks(prevTasks =>
prevTasks.map(group =>
group.id === groupId
? {
...group,
tasks: group.tasks.map(task => (task.id === updatedTask.id ? updatedTask : task)),
}
: group
)
);
};
return (
<GanttChart
tasks={tasks}
onTaskUpdate={handleTaskUpdate}
darkMode={false}
showProgress={true}
editMode={true}
// Optional: Fine-tune editing behavior
// allowProgressEdit={true}
// allowTaskResize={true}
// allowTaskMove={true}
/>
);
}
π Note: Make sure to import the CSS file to apply all necessary styles:
import "react-modern-gantt/dist/index.css";
Without this import, the component will not be styled correctly.
The Gantt chart requires CSS styles that are shipped separately from the component code. You have two options:
// In your application entry point (e.g., App.js or index.js)
import 'react-modern-gantt/dist/index.css';
<!-- In your HTML file -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/index.css" />
GanttChart
: The main component for rendering a Gantt chartTaskItem
: Individual task barsTaskList
: The left sidebar with task groupsTimeline
: The header timeline displayViewModeSelector
: Controls for switching between timeline views
Tooltip
: Information tooltip for tasksTodayMarker
: Vertical line indicating the current date
interface Task {
id: string; // Unique identifier
name: string; // Task name
startDate: Date; // Start date
endDate: Date; // End date
color?: string; // Task color (CSS color value or hex code)
percent?: number; // Completion percentage (0-100)
dependencies?: string[]; // IDs of dependent tasks
[key: string]: any; // Additional custom properties
}
interface TaskGroup {
id: string; // Unique identifier
name: string; // Group name
description?: string; // Group description
icon?: string; // Optional icon (HTML string)
tasks: Task[]; // Array of tasks in this group
[key: string]: any; // Additional custom properties
}
The component supports seven different view modes to adapt to different timeline needs, from granular hour-by-hour scheduling to long-term year planning:
View Mode | Description | Best Used For |
---|---|---|
MINUTE |
Shows minutes | Ultra-detailed short-term planning (minutes/hours) |
HOUR |
Shows hours | Detailed hourly scheduling (hours/days) |
DAY |
Shows days | Detailed short-term planning (days/weeks) |
WEEK |
Shows weeks | Short to medium-term planning (weeks/months) |
MONTH |
Shows months | Medium-term planning (months/quarters) |
QUARTER |
Shows quarters | Medium to long-term planning (quarters/year) |
YEAR |
Shows years | Long-term planning (years) |
import { GanttChart, ViewMode } from "react-modern-gantt";
// Using string literals
<GanttChart tasks={tasks} viewMode="hour" />
// Using the ViewMode enum for hourly view
<GanttChart tasks={tasks} viewMode={ViewMode.HOUR} />
// Enable all view modes including MINUTE and HOUR
<GanttChart
tasks={tasks}
viewMode={ViewMode.HOUR}
viewModes={[
ViewMode.MINUTE,
ViewMode.HOUR,
ViewMode.DAY,
ViewMode.WEEK,
ViewMode.MONTH,
ViewMode.QUARTER,
ViewMode.YEAR,
]}
/>
The Hour and Minute views are perfect for detailed scheduling:
- Hour View: Shows tasks on an hourly timeline, ideal for daily schedules, meeting planning, and shift management
- Minute View: Shows tasks with minute-level precision (configurable step intervals)
// Hourly schedule example
const hourlyTasks = [
{
id: 'today',
name: "Today's Schedule",
tasks: [
{
id: 'meeting-1',
name: 'Team Standup',
startDate: new Date(2024, 0, 15, 9, 0), // 9:00 AM
endDate: new Date(2024, 0, 15, 9, 30), // 9:30 AM
percent: 100,
},
{
id: 'meeting-2',
name: 'Client Meeting',
startDate: new Date(2024, 0, 15, 14, 0), // 2:00 PM
endDate: new Date(2024, 0, 15, 15, 30), // 3:30 PM
percent: 50,
},
],
},
];
<GanttChart tasks={hourlyTasks} viewMode={ViewMode.HOUR} showProgress={true} />;
React Modern Gantt includes a powerful interactive progress editing feature that allows users to adjust task completion percentages directly on the chart with a smooth, intuitive interface.
When editMode={true}
and showProgress={true}
, each task displays a progress bar with an interactive handle (a draggable blob) at the end of the progress fill. Users can:
- Hover over a task to reveal the progress handle
- Drag the handle left or right to adjust the completion percentage
- See a real-time percentage tooltip showing the current value while dragging
- Click anywhere on the progress bar to jump to that percentage
π― Percentage tooltip - Shows exact percentage (e.g., "75%") while dragging π¨ Visual feedback - Handle scales up on hover and during drag π« Conflict-free - Progress editing doesn't interfere with task movement/resizing π Constrained - Automatically clamps values between 0% and 100%
import React, { useState } from 'react';
import GanttChart from 'react-modern-gantt';
import 'react-modern-gantt/dist/index.css';
function App() {
const [tasks, setTasks] = useState([
{
id: 'team-1',
name: 'Development',
tasks: [
{
id: 'task-1',
name: 'Feature Implementation',
startDate: new Date(2024, 0, 1),
endDate: new Date(2024, 0, 15),
percent: 65, // Initial progress
},
],
},
]);
const handleTaskUpdate = (groupId, updatedTask) => {
setTasks(prevTasks =>
prevTasks.map(group =>
group.id === groupId
? {
...group,
tasks: group.tasks.map(task => (task.id === updatedTask.id ? updatedTask : task)),
}
: group
)
);
// Log progress updates
console.log(`Progress updated: ${updatedTask.name} - ${updatedTask.percent}%`);
};
return (
<GanttChart
tasks={tasks}
editMode={true} // Enable editing
showProgress={true} // Show progress bars
onTaskUpdate={handleTaskUpdate}
/>
);
}
You can customize the progress bar appearance using CSS variables:
:root {
/* Progress bar styling */
--rmg-progress-bg: rgba(0, 0, 0, 0.2); /* Background track */
--rmg-progress-fill: white; /* Progress fill color */
/* Progress handle (draggable blob) */
--rmg-task-color: #3b82f6; /* Handle border color */
}
/* Custom progress tooltip styling */
.rmg-progress-tooltip {
background-color: var(--rmg-tooltip-bg);
color: var(--rmg-tooltip-text);
border: 1px solid var(--rmg-tooltip-border);
font-weight: 600;
}
- Enable both
editMode
andshowProgress
for interactive progress editing - Handle updates properly in
onTaskUpdate
to persist changes - Combine with hourly view for detailed daily task tracking
- Use animations to provide smooth visual feedback (enabled by default)
You have granular control over different editing features:
Prop | Description | Default | Notes |
---|---|---|---|
editMode |
Global master switch for ALL editing | true |
When false , disables everything |
showProgress |
Shows/hides progress bars | false |
Visual display only |
allowProgressEdit |
Enables progress bar editing | true |
Requires editMode=true AND showProgress=true |
allowTaskResize |
Enables task resizing (left/right handles) | true |
Requires editMode=true |
allowTaskMove |
Enables task movement (drag & drop) | true |
Requires editMode=true |
Important: All granular permissions (allowProgressEdit
, allowTaskResize
, allowTaskMove
) are ignored when editMode={false}
.
// 1. Fully editable (default behavior - no props needed)
<GanttChart tasks={tasks} />
// 2. Read-only with visible progress (no editing at all)
<GanttChart
tasks={tasks}
editMode={false}
showProgress={true}
/>
// 3. Tasks movable but NOT resizable, no progress editing
<GanttChart
tasks={tasks}
editMode={true}
allowTaskResize={false}
showProgress={false}
/>
// 4. Progress visible but NOT editable, tasks fully editable
<GanttChart
tasks={tasks}
editMode={true}
showProgress={true}
allowProgressEdit={false}
/>
// 5. Tasks resizable but NOT movable
<GanttChart
tasks={tasks}
editMode={true}
allowTaskMove={false}
allowTaskResize={true}
/>
// 6. Only progress editing allowed (no task movement/resizing)
<GanttChart
tasks={tasks}
editMode={true}
showProgress={true}
allowTaskMove={false}
allowTaskResize={false}
allowProgressEdit={true}
/>
The component uses a hierarchical permission system:
editMode (master switch)
ββ IF true:
β ββ allowTaskMove β enables/disables task movement
β ββ allowTaskResize β enables/disables resize handles
β ββ allowProgressEdit + showProgress β enables/disables progress editing
ββ IF false:
ββ ALL editing disabled (read-only mode)
React Modern Gantt includes an Infinite Scroll feature that automatically extends the timeline when tasks are dragged beyond the visible range. This is perfect for dynamic project management where timelines need to adapt to changing schedules.
When enabled, the timeline automatically extends when:
- A task is dragged to the left edge (extends timeline backwards)
- A task is dragged to the right edge (extends timeline forwards)
The extension amount is intelligent and adapts to the current view mode:
View Mode | Extension Amount |
---|---|
Minute | +60 minutes (1 hour) |
Hour | +24 hours (1 day) |
Day | +7 days (1 week) |
Week | +4 weeks (~1 month) |
Month | +3 months |
Quarter | +4 quarters (1 year) |
Year | +5 years |
import React, { useState } from 'react';
import GanttChart from 'react-modern-gantt';
import { addMonths, subMonths } from 'date-fns';
function App() {
const [tasks, setTasks] = useState([...]);
const [startDate, setStartDate] = useState(subMonths(new Date(), 2));
const [endDate, setEndDate] = useState(addMonths(new Date(), 4));
const handleTimelineExtend = (direction, newStartDate, newEndDate) => {
console.log(`Timeline extended ${direction}:`, newStartDate, newEndDate);
// Update timeline boundaries
setStartDate(newStartDate);
setEndDate(newEndDate);
// Optional: Load additional data for the new time range
// fetchTasksForRange(newStartDate, newEndDate).then(setTasks);
};
return (
<GanttChart
tasks={tasks}
startDate={startDate}
endDate={endDate}
infiniteScroll={true}
onTimelineExtend={handleTimelineExtend}
editMode={true}
/>
);
}
Prop | Type | Default | Description |
---|---|---|---|
tasks |
TaskGroup[] |
Required | Array of task groups with nested tasks |
viewMode |
ViewMode |
DAY |
Current view mode (MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR) |
viewModes |
ViewMode[] |
All modes | Available view modes in selector |
editMode |
boolean |
true |
Master switch - enables/disables ALL editing features |
allowProgressEdit |
boolean |
true |
Allows progress bar editing (requires editMode=true ) |
allowTaskResize |
boolean |
true |
Allows task resizing with handles (requires editMode=true ) |
allowTaskMove |
boolean |
true |
Allows task movement via drag & drop (requires editMode=true ) |
showProgress |
boolean |
false |
Shows/hides progress bars on tasks |
darkMode |
boolean |
false |
Enables dark mode theme |
onTaskUpdate |
(groupId: string, task: Task) => void |
- | Callback when task is updated |
onTaskClick |
(groupId: string, task: Task) => void |
- | Callback when task is clicked |
startDate |
Date |
Auto-calculated | Timeline start date |
endDate |
Date |
Auto-calculated | Timeline end date |
minuteStep |
number |
5 |
Interval between minute markers (Minute view only) |
infiniteScroll |
boolean |
false |
Enables automatic timeline extension |
onTimelineExtend |
(direction: 'left' | 'right', newStartDate: Date, newEndDate: Date) => void |
- | Callback when timeline extends (used with infiniteScroll ) |
Permission Hierarchy:
editMode={false}
β All editing disabled (read-only mode)editMode={true}
β Individual flags control specific features:allowProgressEdit
β Progress bar editingallowTaskResize
β Task resizing (left/right handles)allowTaskMove
β Task movement (drag & drop)
- Use controlled dates with
startDate
andendDate
props - Update state in
onTimelineExtend
to persist the new timeline range - Consider debouncing if loading data to prevent excessive API calls
- Cache previously loaded data to avoid re-fetching
React Modern Gantt is optimized for performance, especially in detailed views like Minute and Hour modes.
To prevent performance issues with large timelines, the Minute View is automatically limited to 500 intervals. If your time range would generate more than 500 intervals, the component will:
- βοΈ Truncate the timeline to 500 intervals
β οΈ Show a console warning with optimization suggestions
Example:
// This would create ~2880 intervals (24 hours Γ 12 intervals/hour)
<GanttChart
viewMode={ViewMode.MINUTE}
minuteStep={5}
startDate={new Date(2024, 0, 1, 0, 0)}
endDate={new Date(2024, 0, 2, 0, 0)} // 24 hours later
/>
// β Console: "Minute view limited to 500 intervals for performance..."
- For timelines > 40 hours: Use Hour View instead of Minute View
- Increase
minuteStep
: Use 10 or 15 minute intervals for longer ranges - Use smaller time ranges: Keep Minute View for 1-2 days maximum
- Switch view modes: Use Day/Week/Month views for long-term planning
Metric | Before Optimization | After Optimization | Improvement |
---|---|---|---|
Minute View (24h) DOM Elements | ~2,880 | Max 500 | 83% reduction |
Render Time | ~500ms | ~120ms | 76% faster |
Scroll Performance | Laggy | Smooth | Greatly improved |
- π― React.memo on Timeline component for reduced re-renders
- π RequestAnimationFrame for smooth scrolling and animations
- π¦ Minimal DOM updates during drag operations
The easiest way to customize the appearance is by overriding CSS variables:
:root {
/* Primary colors */
--rmg-bg-color: #f8f9fb;
--rmg-text-color: #1a202c;
--rmg-border-color: #e2e8f0;
--rmg-task-color: #3182ce;
--rmg-task-text-color: white;
--rmg-marker-color: #e53e3e;
/* Size variables */
--rmg-row-height: 50px;
--rmg-task-height: 36px;
--rmg-border-radius: 6px;
/* Animation speed */
--rmg-animation-speed: 0.25;
}
<GanttChart
tasks={tasks}
styles={{
container: 'my-gantt-container',
title: 'my-gantt-title',
taskList: 'my-task-list',
timeline: 'my-timeline',
todayMarker: 'my-today-marker',
taskRow: 'my-task-row',
tooltip: 'my-tooltip',
}}
onTaskUpdate={handleTaskUpdate}
/>
<GanttChart
tasks={tasks}
renderTask={({ task, leftPx, widthPx, topPx, isHovered, isDragging, showProgress }) => (
<div
className="my-custom-task"
style={{
position: 'absolute',
left: `${leftPx}px`,
width: `${widthPx}px`,
top: `${topPx}px`,
backgroundColor: task.color || '#3182ce',
}}>
<div className="my-task-label">{task.name}</div>
{showProgress && (
<div className="my-progress-bar">
<div className="my-progress-fill" style={{ width: `${task.percent || 0}%` }} />
</div>
)}
</div>
)}
/>
Handle various interactions with the Gantt chart:
<GanttChart
tasks={tasks}
onTaskUpdate={(groupId, updatedTask) => {
console.log(`Task ${updatedTask.id} updated in group ${groupId}`);
// Update your state here
updateTasks(groupId, updatedTask);
}}
onTaskClick={(task, group) => {
console.log(`Task ${task.id} clicked in group ${group.id}`);
// Do something when a task is clicked
selectTask(task.id);
}}
onTaskSelect={(task, isSelected) => {
console.log(`Task ${task.id} selection state: ${isSelected}`);
// Handle selection state changes
}}
onGroupClick={group => {
console.log(`Group ${group.id} clicked`);
// Do something when a group is clicked
}}
onViewModeChange={viewMode => {
console.log(`View mode changed to: ${viewMode}`);
// Handle view mode changes
}}
/>
Dark mode is built-in and easy to enable:
<GanttChart tasks={tasks} darkMode={true} onTaskUpdate={handleTaskUpdate} />
<GanttChart
tasks={tasks}
getTaskColor={({ task }) => {
// Task is complete
if (task.percent === 100) {
return {
backgroundColor: '#22c55e', // Green
borderColor: '#166534',
textColor: '#ffffff',
};
}
// Task has dependencies
if (task.dependencies?.length > 0) {
return {
backgroundColor: '#f59e0b', // Orange
textColor: '#ffffff',
};
}
// High priority task
if (task.priority === 'high') {
return {
backgroundColor: '#ef4444', // Red
textColor: '#ffffff',
};
}
// Default color
return {
backgroundColor: '#3b82f6', // Blue
textColor: '#ffffff',
};
}}
/>
<GanttChart
tasks={tasks}
renderTooltip={({ task, position, dragType, startDate, endDate }) => (
<div className="custom-tooltip">
<h3>{task.name}</h3>
{dragType && <div className="drag-indicator">{dragType === 'move' ? 'Moving task...' : 'Resizing task...'}</div>}
<div className="date-range">
{format(startDate, 'MMM d, yyyy')} - {format(endDate, 'MMM d, yyyy')}
</div>
<div className="progress-section">
<div className="progress-label">Progress: {task.percent || 0}%</div>
<div className="progress-bar">
<div className="progress-fill" style={{ width: `${task.percent || 0}%` }} />
</div>
</div>
{task.assignee && <div className="assignee">Assigned to: {task.assignee}</div>}
</div>
)}
/>
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
Yes, you can use the locale
prop to change the date formatting:
<GanttChart
tasks={tasks}
locale="de-DE" // For German formatting
/>
The Gantt chart is a controlled component, so updates are handled through the onTaskUpdate
callback:
const handleTaskUpdate = (groupId, updatedTask) => {
setTasks(prevTasks =>
prevTasks.map(group =>
group.id === groupId
? {
...group,
tasks: group.tasks.map(task => (task.id === updatedTask.id ? updatedTask : task)),
}
: group
)
);
};
Yes, set the editMode
prop to false
:
<GanttChart tasks={tasks} editMode={false} />
This will disable ALL editing: task movement, resizing, and progress editing.
Yes! You have granular control over different editing features:
// Disable only progress editing (tasks still movable/resizable)
<GanttChart
tasks={tasks}
editMode={true}
showProgress={true}
allowProgressEdit={false}
/>
// Disable task resizing (can move, can't resize)
<GanttChart
tasks={tasks}
editMode={true}
allowTaskResize={false}
/>
// Disable task movement (can resize, can't move)
<GanttChart
tasks={tasks}
editMode={true}
allowTaskMove={false}
/>
// Enable ONLY progress editing (no task editing)
<GanttChart
tasks={tasks}
editMode={true}
showProgress={true}
allowTaskMove={false}
allowTaskResize={false}
/>
Set the showProgress
prop to false
(it's false
by default):
<GanttChart tasks={tasks} showProgress={false} />
To enable progress indicators, set it to true
:
<GanttChart tasks={tasks} showProgress={true} editMode={true} />
Note: Progress bars are only editable when both editMode={true}
AND showProgress={true}
.
Set infiniteScroll={true}
and provide the onTimelineExtend
callback:
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(addMonths(new Date(), 6));
<GanttChart
tasks={tasks}
startDate={startDate}
endDate={endDate}
infiniteScroll={true}
onTimelineExtend={(direction, newStart, newEnd) => {
setStartDate(newStart);
setEndDate(newEnd);
}}
/>;
For performance reasons, Minute View is automatically limited to 500 intervals. This prevents performance issues with large timelines. If you need to display longer time ranges:
- Use a larger
minuteStep
(10 or 15 minutes instead of 5) - Switch to Hour View for timelines longer than ~40 hours
- Break your timeline into smaller chunks
Yes, use the getTaskColor
function:
<GanttChart
tasks={tasks}
getTaskColor={({ task }) => ({
backgroundColor: task.isUrgent ? '#ef4444' : '#3b82f6',
textColor: 'white',
})}
/>
If your Gantt chart appears without styling, make sure you've imported the CSS file:
import 'react-modern-gantt/dist/index.css';
This import should be included in your application's entry point or in the component where you use the Gantt chart.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.