Skip to content

Draggable Markers

Add one or more draggable vertical markers — styled like the built-in today line — and use the drag callbacks to drive project-specific overlays such as history playback, baselines, or audit scrubbers.

Basic setup

tsx
import { useMemo, useState } from 'react';
import { format } from 'date-fns';
import { GanttChart } from 'react-gantt-lib';
import type { DraggableMarker } from 'react-gantt-lib';

const toDateString = (date: Date) => format(date, 'yyyy-MM-dd');

const [marker, setMarker] = useState<DraggableMarker>({
  id: 'as-of',
  date: '2026-02-20',
  label: 'As of',
  color: '#6366f1',
});

const draggableMarkers = useMemo(() => [marker], [marker]);

<GanttChart
  tasks={tasks}
  minDate="2026-01-26"
  maxDate="2026-04-05"
  draggableMarkers={draggableMarkers}
  onDraggableMarkerDrag={({ date }) =>
    setMarker((m) => ({ ...m, date: toDateString(date) }))
  }
  onDraggableMarkerDragEnd={({ date, previousDate }) => {
    const iso = toDateString(date);
    setMarker((m) => ({ ...m, date: iso }));
    console.log('scrubbed', previousDate, '→', date);
  }}
/>

Date strings

Use local calendar dates (format(date, 'yyyy-MM-dd')), not date.toISOString().slice(0, 10) — UTC conversion can shift the marker off the grid after release.

Custom snap points

Pass invisible snap targets when the marker should jump between known dates/times (saved revisions, audit checkpoints, baseline snapshots). Snap points are not rendered — they only affect drag snapping.

When draggableMarkerSnapPoints is provided, it takes priority over snapToGrid.

tsx
import type { DraggableMarkerSnapPoint } from 'react-gantt-lib';

const revisionSnapPoints: DraggableMarkerSnapPoint[] = [
  { id: 'rev-1', date: '2026-02-02' },
  { id: 'rev-2', date: '2026-02-14T09:00:00' }, // datetime supported
  { id: 'rev-3', date: '2026-03-01' },
  { id: 'rev-4', date: '2026-03-29' },
];

<GanttChart
  tasks={tasks}
  minDate="2026-01-26"
  maxDate="2026-04-05"
  draggableMarkers={draggableMarkers}
  draggableMarkerSnapPoints={revisionSnapPoints}
  onDraggableMarkerDragToSnapPoint={({
    snapPoint,
    snapPointIndex,
    date,
    phase,
  }) => {
    // phase: 'drag' while scrubbing, 'end' on pointer release
    loadRevision(snapPoint.id, date);
    console.log(phase, snapPointIndex, snapPoint.id);
  }}
  onDraggableMarkerDragEnd={({ snapPoint, date }) => {
    if (snapPoint) {
      setMarker((m) => ({ ...m, date: toDateString(date) }));
    }
  }}
/>

Snap behaviour

ModeWhenSnaps to
Custom snap pointsdraggableMarkerSnapPoints.length > 0Nearest snap point (by timeline distance)
Grid snap (default)snapToGrid={true} and no snap pointsNearest column boundary (vertical grid line)
Free dragsnapToGrid={false} and no snap pointsExact pointer position

During drag with snap points, the marker magnet-snaps to the nearest point in real time. onDraggableMarkerDragToSnapPoint fires when the active snap target changes (phase: 'drag') and again on pointer release (phase: 'end').

Use onDraggableMarkerDragToSnapPoint to load revision data, diff tasks, or update external history UI. Keep work lightweight during phase: 'drag' if the handler runs often.

Props & callbacks

Prop / callbackDescription
draggableMarkers{ id, date, label?, color?, draggable? }[]id is required
draggableMarkerSnapPoints{ id, date }[] — invisible snap targets; datetime strings supported
onDraggableMarkerDragStartPointer down — { marker, index, date }
onDraggableMarkerDragPointer move — { marker, index, date, previousDate, deltaMs }
onDraggableMarkerDragEndPointer up — { marker, index, date, previousDate, snapPoint?, snapPointIndex? }
onDraggableMarkerDragToSnapPointSnapped to a custom point — { marker, snapPoint, snapPointIndex, date, previousDate, phase }

During drag, marker position updates imperatively in the layer (no full chart re-render). Update your draggableMarkers state from the callbacks to keep the marker controlled after release.

Performance — optional callbacks

Drag handlers are opt-in. Omit any callback you do not need — it will not be invoked, and related work is skipped:

Omitted callbackSkipped work
All drag callbacksNo hit targets, no document listeners, no snap-point resolution — markers render as static lines only
onDraggableMarkerDragNo per-pointermove callback or payload allocation
onDraggableMarkerDragToSnapPointNo snap-point change tracking during drag
draggableMarkerSnapPoints without drag callbacksSnap points are not resolved at all

Register only onDraggableMarkerDragEnd (or onDraggableMarkerDragToSnapPoint) if you only care about the final snap target.

Performance

Avoid heavy work in onDraggableMarkerDrag. Prefer lightweight state updates during phase: 'drag'; defer expensive revision diffs to phase: 'end' or onDraggableMarkerDragEnd.

Guide: Events · ← Examples

Released under the MIT License.