The timeline
Pick the scale, control the date range, and shade weekends and holidays.
The timeline decides how much time fits on screen. Pick the scale it opens at, fit or pin its date range, and shade the days nobody works.
Choose a starting scale
Five scales are built in, finest first. defaultScale picks the one the chart opens at and defaults
to month.
| Scale | Cell holds | Drag step | Px per day | Padding |
|---|---|---|---|---|
day | 6 hours | 1 hour | 288 | 30 hours |
week | 1 day | 6 hours | 72 | 5 days |
month | 7 days | 1 day | 18 | 35 days |
quarter | 1 month | 7 days | 4 | 5 months |
year | 1 quarter | 28 days | 1 | 15 months |
Each step out is about four times coarser, and a header cell stays 60 to 130px wide. A quarter or
year cell follows the calendar. The drag step is the grid a bar snaps to, covered in
Editing tasks.
import { useState } from 'react';
import { ReactGanttChart, type Task } from '@jaeungkim/gantt-chart';
import '@jaeungkim/gantt-chart/style.css';
const initialTasks: Task[] = [
{ id: 'spec', name: 'Spec', parentId: null, sequence: '1',
startDate: '2026-06-10', endDate: '2026-06-13' },
];
export function ProjectChart() {
const [tasks, setTasks] = useState(initialTasks);
return (
<ReactGanttChart
tasks={tasks}
onTasksChange={setTasks}
defaultScale="week"
height={420}
/>
);
}defaultScale is read once at mount; later changes are ignored. onScaleChange reports every
committed change, from the ref, wheel zoom, the keyboard or zoomToFit(), and never on mount.
The chart draws no scale picker. Wire one to setScale on the ref, reading its value back from
onScaleChange. See Imperative API.
Fit the timeline to your data
With no range props the chart fits the timeline to the rolled-up task dates, earliest startDate to
latest endDate, padded by five ticks at each end. Cells start there, snapped back to a tick
boundary, so month week columns open on a Sunday.
The last cell can finish past the padded end. See Task list and hierarchy.
onRangeChange reports the range as two Dayjs values. end is exclusive, the last cell's start
plus one tick, so an inclusive query counts the last tick twice.
It fires on the first render with cells, then whenever those dates change, which a scale change almost always causes. The handler is read through a ref, so replacing it does not re-fire it; a late one sees only the next change.
A vertical line marks today, with a dot in the header above it. No prop adds another marker or
moves, labels or hides this one; CSS on .gantt-today-marker and .gantt-today-dot restyles it.
The date is read when the timeline recomputes, so it never moves at midnight and vanishes when today
leaves the range.
Pin or extend the range
visibleStart and visibleEnd take ISO strings and pin the timeline instead of fitting it. A pinned
start snaps back to a tick boundary; a pinned end is used as given.
Pin both and task dates stop affecting the layout. A task outside the window keeps its row, its bar clamped to the nearest edge at the minimum width.
Pin one end and the other auto-fits with its padding. With no tasks and one end pinned, the chart renders an empty element and throws nothing.
infiniteScroll grows the range as people scroll, off by default. Coming within half a viewport of
either end adds about a viewport of ticks there, up to 2000 per side.
The front is checked first, so a chart mounted at the far left extends straight away. Cells added in front shift the scroll, keeping the view on the same dates. A pinned end never grows, and a scale change drops what has accumulated.
Let people zoom
zoomOnWheel is off by default, leaving Ctrl or Cmd with the wheel to page zoom. Turn it on and
that gesture steps one scale, wheel down to zoom out.
The date under the cursor keeps its x position. A cursor over the task list anchors on the timeline's left edge, and one past the last cell cancels the zoom.
Ctrl or Cmd with ArrowUp steps one scale finer and ArrowDown one coarser, needing no prop. It anchors on the timeline's centre and announces the new scale. See Keyboard and screen readers.
Both stop at the finest and coarsest scale. Plain wheel scrolls vertically and Shift with the wheel
horizontally, either way. zoomToFit() on the ref fits every rendered bar in one call, as
Imperative API shows.
Shade weekends and holidays
showNonWorkingDays is on by default, shading non-working days behind the bars and writing holiday
names in the tick row. workingWeekdays sets the weekly rule in UTC day numbers, 0 being Sunday,
and defaults to Monday to Friday. holidays lists the exceptions, matched against each tick's UTC
date.
const WORKING_WEEKDAYS = [1, 2, 3, 4, 5, 6];
const HOLIDAYS = [
'2026-06-06',
{ date: '2026-09-24', endDate: '2026-09-26', label: 'Chuseok', color: '#7c3aed' },
];
<ReactGanttChart
tasks={tasks}
onTasksChange={setTasks}
workingWeekdays={WORKING_WEEKDAYS}
holidays={HOLIDAYS}
/>Both arrays are read by identity, so a fresh literal each render rebuilds the calendar. Keep them in
a module constant or a useMemo.
A bare YYYY-MM-DD string is a day off with no name. The object form adds an inclusive endDate, a
label and a color. Where two entries cover a day, the earlier one wins.
A name appears in the tick row only where its band is at least 44px wide, so the shutdown above is named and the one-day offsite is not. Hover a narrower band in the header to read it.
Where two names open in the same cell, only the first is shown. Names are hidden with opacity
rather than display, so a screen reader announces them anyway.
A color is tinted at the weekend's weight, and a holiday band stays separate from the weekend
beside it. One on a day already off keeps its name and colour.
CSS on .gantt-holiday-cell restyles every name. color is the only per-holiday style, and nothing
adds a class or an icon to one holiday.
Shading follows the tick, not the scale name. day, week and month shade, quarter and year
do not, because a month tick cannot be half non-working. Each day in a multi-day cell is checked on
its own, and adjacent non-working days merge into one band.
Before you integrate
- Zoom is five fixed steps, with no pixels-per-day prop, no fractional zoom and no custom scale.
defaultScaleis not persisted. A reload starts there again, so store the scale yourself and pass it back.showNonWorkingDaysshades days and nothing else.falsehides the shading and the names, and a dropped bar lands on a Saturday unlessworkingCalendaris on. See Working calendar.firstDayOfWeekregroups theweekscale's top header only. It moves no shading, andmonthweek columns still open on a Sunday.holidaysentries are read leniently. A range expands to at most 366 days, a backwardsendDatecollapses to its start day, and an unparseabledatedrops it.
See GanttProps for the props and Scale and theme types for the Holiday
shape.