Skip to content

MikaStiebitz/React-Modern-Gantt

Repository files navigation

React Modern Gantt

A flexible, customizable Gantt chart component for React applications with drag-and-drop task scheduling, dark mode support, progress tracking, and multiple view modes.

npm version license bundle size

React Modern Gantt in Dark Mode

Live Demo


πŸ“‹ Table of Contents

✨ Features

  • πŸ“Š 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)

πŸ“¦ Installation

NPM

npm install react-modern-gantt

Yarn

yarn add react-modern-gantt

πŸš€ Quick Start

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.

Using CSS styles

The Gantt chart requires CSS styles that are shipped separately from the component code. You have two options:

Option 1: Import CSS file (Recommended)

// 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

<!-- In your HTML file -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/index.css" />

🧩 Components

Main Components

  • 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

Utility Components

  • Tooltip: Information tooltip for tasks
  • TodayMarker: Vertical line indicating the current date

πŸ“Š Task & TaskGroup Data Structure

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
}

πŸ•’ View Modes

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,
    ]}
/>

Hour and Minute Views

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} />;

πŸ“Š Interactive Progress Editing

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.

How It Works

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

Features

🎯 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%

Usage Example

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}
    />
  );
}

Styling the Progress Bar

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;
}

Best Practices

  • Enable both editMode and showProgress 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)

Toggling Progress Editing

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

// 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}
/>

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)

πŸ”„ Infinite Scroll

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.

How It Works

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

Usage Example

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}
    />
  );
}

Props

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 editing
    • allowTaskResize β†’ Task resizing (left/right handles)
    • allowTaskMove β†’ Task movement (drag & drop)

Best Practices

  • Use controlled dates with startDate and endDate 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

⚑ Performance Optimization

React Modern Gantt is optimized for performance, especially in detailed views like Minute and Hour modes.

Minute View Optimization

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:

// 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..."

Recommendations

  • 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

Performance Metrics

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

Additional Optimizations

  • 🎯 React.memo on Timeline component for reduced re-renders
  • πŸš€ RequestAnimationFrame for smooth scrolling and animations
  • πŸ“¦ Minimal DOM updates during drag operations

🎨 Customization

CSS Variables

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;
}

Custom Styles

<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}
/>

Custom Rendering

<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>
  )}
/>

🎯 Event Handling

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

Dark mode is built-in and easy to enable:

<GanttChart tasks={tasks} darkMode={true} onTaskUpdate={handleTaskUpdate} />

πŸ”„ Advanced Examples

Custom Task Rendering by Status

<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',
    };
  }}
/>

Custom Tooltip for Detailed Information

<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>
  )}
/>

🌐 Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

❓ FAQ

Can I change the date format in the timeline?

Yes, you can use the locale prop to change the date formatting:

<GanttChart
  tasks={tasks}
  locale="de-DE" // For German formatting
/>

How do I handle updates to tasks?

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
    )
  );
};

Can I make the Gantt chart read-only?

Yes, set the editMode prop to false:

<GanttChart tasks={tasks} editMode={false} />

This will disable ALL editing: task movement, resizing, and progress editing.

Can I disable specific editing features?

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}
/>

How do I disable progress indicators?

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}.

How do I enable Infinite Scroll?

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);
  }}
/>;

Why is my Minute View limited to 500 intervals?

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

Can I customize the visual appearance of specific tasks?

Yes, use the getTaskColor function:

<GanttChart
  tasks={tasks}
  getTaskColor={({ task }) => ({
    backgroundColor: task.isUrgent ? '#ef4444' : '#3b82f6',
    textColor: 'white',
  })}
/>

Why are my styles not loading?

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.

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.