@jaeungkim/gantt-chart

Markers and range bands

`GanttMarker`, `GanttRangeBand`, `GanttDateRange`

GanttMarker draws a vertical line at one date. GanttRangeBand shades a span of dates behind the rows. GanttDateRange is the shape onRangeChange hands back. All three are exported from the package root.

// src/MarkerTypes.tsx
import type {
  GanttMarker,
  GanttRangeBand,
  GanttDateRange,
} from '@jaeungkim/gantt-chart';

They reach the chart through three props on GanttProps:

PropTypeDefault
markersGanttMarker[][] (module-level constant, stable across renders)
rangeBandsGanttRangeBand[][] (module-level constant, stable across renders)
onRangeChange(range: GanttDateRange) => voidnone

What markers and bands are used for, and how they behave as the timeline scrolls, is in The timeline.

Date input

Every date field on these types takes the same union:

// src/types/gantt.ts
/** Anything the marker/band props accept as a date */
export type GanttDateInput = string | Date | Dayjs;

GanttDateInput is not exported from the package root — do not try to import it. Write the literal union, or use GanttMarker['date'].

Each value is passed straight to the chart's UTC dayjs, so a string without a zone ('2025-06-01', '2025-06-01T09:00') is read as a UTC wall clock and lands where it reads. The same rules as task dates apply; see Task data.

GanttMarker

// src/types/gantt.ts
/** A labelled vertical line at one date - deadlines, releases, and the built-in today line */
export interface GanttMarker {
  /** React key (default: the date) */
  id?: string;
  date: GanttDateInput;
  /** Text shown at the top of the line - omitted, the line is drawn bare */
  label?: string;
  /** Extra class on the marker element */
  className?: string;
  /** Line color - any CSS color, overrides the class and the theme default */
  color?: string;
  /**
   * Turn the marker into a warning (`data-warning="true"`) once a task ends past its date
   *
   * Checks every task, or only `taskIds` when that is given.
   */
  warnOnOverrun?: boolean;
  /** Limits `warnOnOverrun` to these tasks */
  taskIds?: string[];
}
FieldTypeRequiredDefaultMeaning
idstringno`${String(date)}-${index}`React key only. Never written to the DOM.
dateGanttDateInputyesWhere the line is drawn. An unparseable value drops the marker.
labelstringnononeText in .gantt-marker-label. Omitted, no label element is rendered.
classNamestringnononeAppended after gantt-marker on the line element.
colorstringnovar(--gantt-marker)Any CSS color. Written inline as --gantt-marker-color.
warnOnOverrunbooleannofalseCompared with === true. Sets data-warning="true" when a watched task ends after date.
taskIdsstring[]noall tasksRestricts the warnOnOverrun check to these task ids.

Overrun

The chart resolves warnOnOverrun against its full transformed task list, collapsed rows included. The comparison is strictly greater than:

// src/utils/timeline.ts
const overrun =
  marker.warnOnOverrun === true &&
  tasks.some(
    (task) =>
      (!marker.taskIds || marker.taskIds.includes(task.id)) &&
      dayjs(task.endDate).valueOf() > time
  );

A task ending exactly on date is not an overrun.

DOM

<div class="gantt-marker <className>" style="left: <n>px; --gantt-marker-color: <color>"
     data-warning="true" aria-hidden="true">
  <span class="gantt-marker-label"><label></span>
</div>

data-warning is present only when the overrun check passed; otherwise the attribute is absent, not "false". --gantt-marker-color is present only when color is set. The marker elements are siblings inside .gantt-content, with no wrapper layer.

SelectorDeclarations
.gantt-markerposition:absolute; top:0; bottom:0; width:2px; margin-left:-1px; background:var(--gantt-marker-color, var(--gantt-marker)); pointer-events:none; z-index:2
.gantt-marker[data-warning="true"]--gantt-marker-color: var(--gantt-marker-warning)
.gantt-marker-labelposition:absolute; top:2px; left:3px; padding:1px 5px; font-size:10px; font-weight:600; line-height:1.4; color:var(--gantt-marker-label); background:var(--gantt-marker-color, var(--gantt-marker)); border-radius:3px; white-space:nowrap
.gantt-today-markerposition:absolute; top:0; bottom:0; width:2px; margin-left:-1px; background:var(--gantt-today-marker); opacity:0.7; pointer-events:none; z-index:2

The built-in today marker

The chart prepends one marker of its own to whatever markers holds:

// src/components/Gantt.tsx
{ id: "today", date: dayjs(), className: "gantt-today-marker" }

Its id is "today" and its class list is gantt-marker gantt-today-marker. It carries no label, no color and no warnOnOverrun. There is no prop that hides it — .gantt-today-marker { display: none } is the only switch.

GanttRangeBand

// src/types/gantt.ts
/** A shaded band covering a date range - sprints, phases, freezes */
export interface GanttRangeBand {
  /** React key (default: the start date) */
  id?: string;
  startDate: GanttDateInput;
  endDate: GanttDateInput;
  /** Text shown at the top of the band */
  label?: string;
  /** Extra class on the band element */
  className?: string;
  /** Fill color - any CSS color, overrides the class and the theme default */
  color?: string;
}
FieldTypeRequiredDefaultMeaning
idstringno`${String(startDate)}-${index}`React key only. Never written to the DOM.
startDateGanttDateInputyesLeft edge, measured like a bar starting on that date.
endDateGanttDateInputyesRight edge. Must be strictly after startDate.
labelstringnononeText in .gantt-range-band-label. Omitted, no label element is rendered.
classNamestringnononeAppended after gantt-range-band on the band element.
colorstringnovar(--gantt-band-bg)Any CSS color. Written inline as --gantt-band-color.

DOM

<div class="gantt-range-band-layer" aria-hidden="true">
  <div class="gantt-range-band <className>"
       style="left: <n>px; width: <n>px; --gantt-band-color: <color>">
    <span class="gantt-range-band-label"><label></span>
  </div>
</div>

Bands render no data attributes. The layer element is omitted entirely when no band survives placement.

SelectorDeclarations
.gantt-range-band-layerposition:absolute; top:0; left:0; width:100%; height:100%; pointer-events:none; z-index:0
.gantt-range-bandposition:absolute; top:0; bottom:0; background:var(--gantt-band-color, var(--gantt-band-bg))
.gantt-range-band-labelposition:absolute; top:2px; left:4px; font-size:10px; font-weight:600; color:var(--gantt-muted-foreground); white-space:nowrap

GanttDateRange

// src/types/gantt.ts
/** The rendered timeline range, as reported by `onRangeChange` */
export interface GanttDateRange {
  start: Dayjs;
  end: Dayjs;
}
FieldTypeMeaning
startDayjsStart date of the first tick on the timeline. Inclusive.
endDayjsStart date of the last tick plus one tick unit. Exclusive.

Both values are UTC dayjs objects. The package does not re-export dayjs or the Dayjs type — import it from dayjs directly.

Placement rules

InputResult
Marker date unparseableMarker dropped, no error
Marker date outside the rendered rangeMarker dropped, no error
Band startDate or endDate unparseableBand dropped, no error
Band endDate <= startDateBand dropped, no error
Band entirely before or after the rendered rangeBand dropped, no error
Band overlapping one end of the rangeClipped to the visible part
Band narrower than 1px at the current scaleDrawn 1px wide

Constraints

  • Markers and bands are decorative. Both are aria-hidden="true" with pointer-events: none, so there is no click, hover, tooltip or focus API and no screen-reader text.
  • Markers render inside the scrolling content, below the sticky header. A marker line never crosses the header rows.
  • The today marker's dayjs() is read when the marker memo recomputes. No timer moves the line at midnight.
  • A host marker with id: "today" collides with the built-in one and produces a duplicate React key warning.
  • color outranks warnOnOverrun. Both write --gantt-marker-color, and color writes it inline, so a marker with both keeps its custom color and never turns amber. data-warning still appears in the DOM.
  • className: "gantt-today-marker" on a host marker overrides color on the line but not on the label, because that rule sets background directly.
  • The rendered range is the tasks' span plus a fixed buffer unless visibleStart, visibleEnd or infiniteScroll widens it. A marker far past the last task is not drawn; see The timeline.
  • GanttDateInput, PositionedMarker, PositionedBand, computeMarkerOffsets and computeBandRects are internal. Only GanttMarker, GanttRangeBand and GanttDateRange are importable from the package.
  • The JSDoc on id says the key defaults to the date. The code appends the array index to it. Neither value reaches the DOM.
  • The timeline — when markers and bands are drawn, and what the rendered range covers.
  • Theming--gantt-marker, --gantt-marker-warning, --gantt-marker-label, --gantt-today-marker, --gantt-band-bg and the rest of the custom-property table.
  • Props — the full GanttProps index.

On this page