Working calendar

Snap a dragged bar forward off weekends and holidays.

Dragging a bar can leave a task starting on a Saturday. Turn on workingCalendar and a dropped bar moves forward to the next working day, using the same days the timeline shades.

Turn on working-day snapping

workingCalendar is a boolean and defaults to false. Left off, a drag counts every day, which is plain calendar arithmetic.

The working week runs Monday to Friday by default. Pass workingWeekdays as weekday numbers, with 0 for Sunday, and holidays for the days off beyond the weekend.

WorkWeekChart.tsx
import { useState } from 'react';
import { ReactGanttChart, type Task } from '@jaeungkim/gantt-chart';
import '@jaeungkim/gantt-chart/style.css';

const HOLIDAYS = ['2026-09-25'];

const initialTasks: Task[] = [
  { id: 'audit', name: 'Content audit', parentId: null, sequence: '1',
    startDate: '2026-09-01', endDate: '2026-09-04' },
];

export function WorkWeekChart() {
  const [tasks, setTasks] = useState(initialTasks);

  return (
    <ReactGanttChart
      tasks={tasks}
      onTasksChange={setTasks}
      workingCalendar
      holidays={HOLIDAYS}
      showTaskList
      height={420}
    />
  );
}

A day is non-working when workingWeekdays does not list its weekday, or when holidays covers its UTC date. The shading and the snap read that one definition, so a shaded day is always a skipped day. The timeline covers what those two props shade.

A holiday's label and color never reach the snap. A named holiday and a plain Saturday move a drop by the same number of days.

workingWeekdays and holidays are the whole configuration. No prop takes a calendar object, so a rule those two cannot express, such as every second Friday off, stays outside the snap. For your own date math, use createWorkingCalendar from Working calendar helpers.

Move or resize a bar

Which edge snaps depends on the gesture.

GestureEdge that snapsThe other edge
MoveStartFollows by the same days, so the span is kept
Left resizeStartStays, so the bar comes out shorter
Right resizeEndStays, so the bar comes out longer

Drag the Content audit bar four days later at the month scale and its start lands on Saturday 2026-09-05. The start snaps forward two days to Monday 2026-09-07, the end follows, and the bar keeps its span of three days.

The snap counts whole days and only moves forward, so the time of day is kept. A left resize that would snap past the bar's own end does not snap. Dragging a summary bar carries its whole subtree, as Task list and hierarchy describes.

The snap applies while the pointer is down. The bar and the date readout in the header show the snapped dates, and releasing commits them.

Bar width and shading

A bar that spans a weekend still spans it on screen. The calendar decides where a drop lands, not how a bar is drawn. The columns, the header and the shading are unchanged as well.

Pointer position still converts to a date by counting every day. The snap is added on top, so a move keeps the bar's span in calendar days rather than working days.

Before you integrate

  • The snap runs on a pointer drag of a bar. A new tasks array, a drawn link, a drawn task and a date typed in the detail panel all keep the dates you passed in.
  • Keyboard moves and resizes shift by plain drag steps, so they can leave a bar on a non-working day. See Keyboard and screen readers.
  • minDate and maxDate clamp after the snap and win, so a bar held at a bound can end on a Saturday. Editing tasks covers those bounds.
  • The calendar is data your app passes in. Nothing in the chart edits workingWeekdays or holidays.

Props and defaults: GanttProps.