A modern, customizable Gantt chart component for React applications
npm install react-modern-ganttA 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)
``bash`
npm install react-modern-gantt
`bash`
yarn add react-modern-gantt
`jsx
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 (
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:
#### Option 1: Import CSS file (Recommended)
`js`
// In your application entry point (e.g., App.js or index.js)
import 'react-modern-gantt/dist/index.css';
#### Option 2: Reference CSS in HTML
`html`
- GanttChart: The main component for rendering a Gantt chart
- TaskItem: Individual task bars
- TaskList: The left sidebar with task groups
- Timeline: The header timeline display
- ViewModeSelector: Controls for switching between timeline views
- Tooltip: Information tooltip for tasks
- TodayMarker: Vertical line indicating the current date
`typescript
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) |
`jsx
import { GanttChart, ViewMode } from "react-modern-gantt";
// Using string literals
// Using the ViewMode enum for hourly view
// Enable all view modes including MINUTE and HOUR
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)
`jsx
// 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,
},
],
},
];
`
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:
1. Hover over a task to reveal the progress handle
2. Drag the handle left or right to adjust the completion percentage
3. See a real-time percentage tooltip showing the current value while dragging
4. 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%
`jsx
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 (
editMode={true} // Enable editing
showProgress={true} // Show progress bars
onTaskUpdate={handleTaskUpdate}
/>
);
}
`
You can customize the progress bar appearance using CSS variables:
`css
: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 and showProgress for interactive progress editingonTaskUpdate
- Handle updates properly in 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}.
#### Common Configurations
`jsx
// 1. Fully editable (default behavior - no props needed)
// 2. Read-only with visible progress (no editing at all)
editMode={false}
showProgress={true}
/>
// 3. Tasks movable but NOT resizable, no progress editing
editMode={true}
allowTaskResize={false}
showProgress={false}
/>
// 4. Progress visible but NOT editable, tasks fully editable
editMode={true}
showProgress={true}
allowProgressEdit={false}
/>
// 5. Tasks resizable but NOT movable
editMode={true}
allowTaskMove={false}
allowTaskResize={true}
/>
// 6. Only progress editing allowed (no task movement/resizing)
editMode={true}
showProgress={true}
allowTaskMove={false}
allowTaskResize={false}
allowProgressEdit={true}
/>
`
#### Permission Logic
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 |
`jsx
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 (
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 and endDate propsonTimelineExtend
- Update state in 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:
1. āļø Truncate the timeline to 500 intervals
2. ā ļø Show a console warning with optimization suggestions
Example:
`jsx`
// This would create ~2880 intervals (24 hours Ć 12 intervals/hour)
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:
`css
: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;
}
`
`jsx`
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}
/>
`jsx${leftPx}px
renderTask={({ task, leftPx, widthPx, topPx, isHovered, isDragging, showProgress }) => (
className="my-custom-task"
style={{
position: 'absolute',
left: ,${widthPx}px
width: ,${topPx}px
top: ,`
backgroundColor: task.color || '#3182ce',
}}>
{task.name}
{showProgress && (
${task.percent || 0}% }} />
)}
)}
/>
Handle various interactions with the Gantt chart:
`jsxTask ${updatedTask.id} updated in group ${groupId}
onTaskUpdate={(groupId, updatedTask) => {
console.log();Task ${task.id} clicked in group ${group.id}
// Update your state here
updateTasks(groupId, updatedTask);
}}
onTaskClick={(task, group) => {
console.log();Task ${task.id} selection state: ${isSelected}
// Do something when a task is clicked
selectTask(task.id);
}}
onTaskSelect={(task, isSelected) => {
console.log();Group ${group.id} clicked
// Handle selection state changes
}}
onGroupClick={group => {
console.log();View mode changed to: ${viewMode}
// Do something when a group is clicked
}}
onViewModeChange={viewMode => {
console.log();`
// Handle view mode changes
}}
/>
Dark mode is built-in and easy to enable:
`jsx`
`jsx
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',
};
}}
/>
`
` {dragType && {task.assignee && - Chrome (latest) Yes, you can use the The Gantt chart is a controlled component, so updates are handled through the Yes, set the This will disable ALL editing: task movement, resizing, and progress editing. Yes! You have granular control over different editing features: // Disable task resizing (can move, can't resize) // Disable task movement (can resize, can't move) // Enable ONLY progress editing (no task editing) Set the To enable progress indicators, set it to Note: Progress bars are only editable when both Set 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 Yes, use the If your Gantt chart appears without styling, make sure you've imported the CSS file: 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. 1. Fork the repository This project is licensed under the MIT License - see the LICENSE file for details.jsx
renderTooltip={({ task, position, dragType, startDate, endDate }) => (
{task.name}
{dragType === 'move' ? 'Moving task...' : 'Resizing task...'}}
{format(startDate, 'MMM d, yyyy')} - {format(endDate, 'MMM d, yyyy')}
Progress: {task.percent || 0}%
${task.percent || 0}% }} />
Assigned to: {task.assignee}}
)}
/>
`localeš Browser Support
- Firefox (latest)
- Safari (latest)
- Edge (latest)ā FAQ
$3
prop to change the date formatting:`jsx`
locale="de-DE" // For German formatting
/>onTaskUpdate$3
callback:`jsx`
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
)
);
};editMode$3
prop to false:`jsx``$3
jsx`
// Disable only progress editing (tasks still movable/resizable)
editMode={true}
showProgress={true}
allowProgressEdit={false}
/>
editMode={true}
allowTaskResize={false}
/>
editMode={true}
allowTaskMove={false}
/>
editMode={true}
showProgress={true}
allowTaskMove={false}
allowTaskResize={false}
/>showProgress$3
prop to false (it's false by default):`jsx`true:`jsx`editMode={true} AND showProgress={true}.infiniteScroll={true}$3
and provide the onTimelineExtend callback:`jsx`
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(addMonths(new Date(), 6));
startDate={startDate}
endDate={endDate}
infiniteScroll={true}
onTimelineExtend={(direction, newStart, newEnd) => {
setStartDate(newStart);
setEndDate(newEnd);
}}
/>;minuteStep$3
(10 or 15 minutes instead of 5)getTaskColor
- Switch to Hour View for timelines longer than ~40 hours
- Break your timeline into smaller chunks$3
function:`jsx`
getTaskColor={({ task }) => ({
backgroundColor: task.isUrgent ? '#ef4444' : '#3b82f6',
textColor: 'white',
})}
/>`$3
js`
import 'react-modern-gantt/dist/index.css';git checkout -b feature/amazing-featureš¤ Contributing
2. Create your feature branch ()git commit -m 'Add some amazing feature'
3. Commit your changes ()git push origin feature/amazing-feature`)
4. Push to the branch (
5. Open a Pull Requestš License