npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ganttloom/gantt-core

v0.6.2

Published

Framework-agnostic, fully customizable Gantt chart engine: grouping, drag-drop, dependency linking, expand/collapse, theming.

Readme

@ganttloom/gantt-core

npm license

Framework-agnostic Gantt chart engine: layout, SVG rendering, drag/resize/link interactions, grouping, critical path, undo/redo, and more — zero runtime dependencies. If you're using React or Vue, prefer @ganttloom/gantt-react or @ganttloom/gantt-vue; use this package directly if you're integrating with something else (Svelte, vanilla JS) or want full control over the DOM lifecycle.

ganttloom overview — day-view Gantt chart with dependencies, critical path, and baselines

Install

npm install @ganttloom/gantt-core

Quick start

import { GanttChart } from "@ganttloom/gantt-core";
import "@ganttloom/gantt-core/styles.css"; // or call injectStyles() at runtime

const container = document.getElementById("gantt")!;

const chart = new GanttChart(
  container,
  [
    { id: "1", name: "Design", start: new Date("2026-01-05"), end: new Date("2026-01-09"), progress: 60 },
    { id: "2", name: "Build", start: new Date("2026-01-09"), end: new Date("2026-01-16"), progress: 10 },
  ],
  [{ fromId: "1", toId: "2", type: "FS" }],
  { viewMode: "day", showCriticalPath: true, enableHistory: true }
);

chart.on("date-change", ({ task, start, end }) => {
  console.log(`${task.name} moved to ${start} - ${end}`);
});

Tasks and dependencies are plain objects — the chart never owns your state. Call chart.setTasks(...) / chart.setDependencies(...) whenever your data changes; call chart.destroy() when you're done with it.

Core concepts

  • GanttTask — id, name, start/end (Date), optional progress, parentId (for grouping), isGroup, isMilestone, collapsed, color/ progressColor, readonly, baselineStart/baselineEnd (plus baselines for multiple named snapshots), deadline, constraintType / constraintDate, assignees, segments (split/multi-bar rendering), notes, and a free-form data bag.
  • GanttDependency — fromId, toId, a type of "FS" | "SS" | "FF" | "SF", and an optional lagMs.
  • GanttColumn — describes one column of the left-hand grid panel, with an optional accessor to derive its cell value from a task, getHref to render it as a link, render for full custom cell content, align, and sortable.
  • GanttOptions — everything else: view mode, theme, readonly, which optional features are on, and the event callbacks. See src/types.ts for the full, always-current list — it's the source of truth.

Feature options (all opt-in)

| Option | What it does | |---|---| | showCriticalPath | Highlights the critical path (longest dependency chain) | | showBaseline | Renders baselineStart/baselineEnd as a faint bar behind the live one | | showDeadlines (default true) | Renders task.deadline as a flag marker; red when the task's end is past it | | showAssigneeAvatars | Renders task.assignees as small avatars/initials at the bar's left edge | | enableHistory | Turns on chart.undo() / chart.redo() for move/resize/link/progress/create actions | | keyboardAccessible | ARIA roles/labels on bars + arrow-key movement | | virtualScroll (default true) | Only renders DOM/SVG nodes for rows in the visible viewport | | pagination | { pageSize, page } — render only a page of top-level rows | | autoSchedule | Moving/resizing a task cascades date shifts to dependent successors | | calendar | A WorkingCalendar (working days + holidays) — shades non-working time and, with autoSchedule, skips it when cascading | | snapToUnit | Round drag/resize deltas to "hour" \| "day" \| "week", or false for raw pixels | | colorScheme | "light" \| "dark" \| "auto" (default "auto", follows the OS preference live) | | headerPosition | "top" \| "bottom" \| "none" (default "top") — positions the grid and timeline headers |

Working-time calendars

import type { WorkingCalendar } from "@ganttloom/gantt-core";

const calendar: WorkingCalendar = {
  workingDays: [false, true, true, true, true, true, false], // Sun..Sat; this is also the default
  holidays: ["2026-01-19", "2026-02-16"], // ISO "YYYY-MM-DD"
};

new GanttChart(container, tasks, deps, { calendar, autoSchedule: true });

Non-working days are shaded in the timeline. With autoSchedule on, a successor task pushed by a dependency cascade is nudged forward to the next working day. Standalone helpers (isWorkingDay, isHoliday, nextWorkingDay, previousWorkingDay, shiftToWorkingDay) are also exported if you need the same logic outside the chart.

Task constraints

MSP-style constraints (constraintType + constraintDate on a GanttTask) are enforced during autoSchedule cascades — when a dependency pushes a constrained successor, its constraint is applied on top of the cascade before the new dates are committed:

{ id: "3", name: "Vendor delivery", start, end, constraintType: "mso", constraintDate: new Date("2026-02-01") }

ConstraintType is one of "asap" | "alap" | "snet" | "snlt" | "fnet" | "fnlt" | "mso" | "mfo" (as-soon-as-possible / as-late-as-possible / start-no-earlier/later-than / finish-no-earlier/later-than / must-start/finish-on). "asap"/"alap" are no-ops — a true as-late-as-possible schedule needs a backward pass from the project end date, which this incremental, per-drag engine doesn't attempt. The standalone applyConstraint(range, type, date) function is exported if you want the same logic elsewhere. Constraints only take effect during a cascade; they don't restrict a direct manual drag of the constrained task itself (matching how MSP flags rather than blocks a violated constraint).

Drag-to-create tasks

Opt in by supplying onTaskCreate: dragging on an empty part of any row's timeline then creates a new sibling task (same parentId as that row) with the dragged date range, inserted right after that row.

new GanttChart(container, tasks, deps, {
  onTaskCreate: (task) => console.log("created", task),
});

Without onTaskCreate, drag-to-create is off — dragging empty timeline space does nothing, so existing consumers see no behavior change.

Resource histogram

import { computeResourceHistogram, renderResourceHistogramSVG } from "@ganttloom/gantt-core";

const buckets = computeResourceHistogram(tasks, { capacityHoursPerDay: 8, calendar });
// [{ assigneeId, assigneeName, date, allocatedHours, capacityHours, overallocated }, ...]

document.getElementById("histogram")!.innerHTML = renderResourceHistogramSVG(buckets);

Each assignee is treated as fully allocated (one day's capacity) on every working day a task they're on spans — there's no per-task effort/FTE field in GanttTask, so that's the only allocation model the plain task data can support. overallocated is true once a bucket's summed hours exceed capacityHours (default 8/day).

WBS numbering

import { computeWBSCodes } from "@ganttloom/gantt-core";

const wbs = computeWBSCodes(tasks); // Map<taskId, "1" | "1.1" | "1.2" | "2" | ...>
const columns = [{ id: "wbs", title: "WBS", accessor: (t) => wbs.get(t.id) }];

Codes are computed fresh from the current tasks array (recompute after any reorder/renest) rather than stored on the task, keeping with "everything is a plain object."

Timeline markers

new GanttChart(container, tasks, deps, {
  markers: [{ date: new Date("2026-02-01"), label: "Sprint boundary" }],
});

Arbitrary labeled vertical lines on the timeline, independent of any task — for release dates, sprint boundaries, or any other project-wide event.

Grid column resize/reorder

Both off by default; supply the corresponding handler to opt in (same construction-time-only wiring caveat as onTaskCreate):

new GanttChart(container, tasks, deps, {
  columns,
  onColumnResize: (columnId, width) => { /* persist the new width */ },
  onColumnReorder: (order) => { /* persist the new column id order */ },
});

Drag a column header's right edge to resize it; drag a header itself to reorder columns. The chart applies the change to its own internal copy of columns immediately (so the drag feels live) and calls back so you can persist it — same pattern as a task drag and onDateChange.

Grid column sorting

Mark a column sortable: true and clicking its header cycles ascending → descending → none, with a ▲/▼ indicator:

new GanttChart(container, tasks, deps, {
  columns: [
    { id: "name", title: "Task", sortable: true },
    { id: "assignee", title: "Assignee", accessor: (t) => t.assignees?.[0]?.name, sortable: true },
  ],
  onSortChange: (sort) => console.log(sort), // { columnId, direction } | null
});

chart.setSort("assignee", "desc"); // or null to clear
chart.getSort(); // { columnId: "assignee", direction: "desc" } | null

Sorting reorders siblings at every tree level by the column's value (via accessor, or task.name for the conventional unaccessored "name" column) — the group/parent hierarchy itself is never reshuffled, only the order within it. A column with neither accessor nor the id "name" has nothing sortable to read and is a no-op if marked sortable.

Header position

new GanttChart(container, tasks, deps, { headerPosition: "bottom" });

headerPosition positions the grid and timeline headers: "top" (the default), "bottom", or "none" to hide them entirely. Can also be changed after construction via chart.setOptions({ headerPosition: "none" }).

Grid column width

A GanttColumn with no explicit width grows to fill whatever space is left in the grid panel after its sibling columns' widths are subtracted — like a CSS minmax(0, 1fr) track (flex: 1 1 0 under the hood) — instead of shrinking to its own content size. With multiple width-less columns, the leftover space is shared evenly between them. A column with an explicit width is unaffected and never grows or shrinks beyond it:

const columns = [
  { id: "name", title: "Name" },              // grows to fill leftover space
  { id: "status", title: "Status", width: 80 }, // fixed at 80px
];

Task search/filter

import { filterTasks } from "@ganttloom/gantt-core";

const { tasks: visible, matchedIds } = filterTasks(tasks, (t) =>
  t.name.toLowerCase().includes(query.toLowerCase())
);
chart.setTasks(visible);

Pure/compute-only — it returns the filtered subset (keeping a matched task's ancestors and descendants by default, so the tree stays intact) and the set of directly-matched ids, which you can use to dim ancestor/descendant rows shown only for tree context. There's no special renderer wiring: feeding chart.setTasks(visible) a smaller array hides the rest, the same as any other plain-data update.

Scroll-to-today, group progress rollup, and custom cells

chart.scrollToToday(); // centers the viewport on today's date

new GanttChart(container, tasks, deps, {
  autoRollupProgress: true, // a group with no explicit `progress` averages its children's, duration-weighted
});

const columns = [
  { id: "status", title: "Status", render: (task) => {
      const el = document.createElement("span");
      el.className = task.progress === 100 ? "badge-done" : "badge-pending";
      el.textContent = task.progress === 100 ? "Done" : "Pending";
      return el; // or return a plain string - never parsed as HTML, so it's XSS-safe by construction
    } },
];

Row reorder and dependency editing

Both off by default; supply the corresponding handler to opt in:

new GanttChart(container, tasks, deps, {
  onTaskReorder: (draggedId, targetId, position) => {
    // position: "before" | "after" (reorder as a sibling) | "inside" (reparent under target)
  },
  onDependencyDblClick: (dep) => {
    // open your own "edit this link" UI, then call:
    chart.updateDependency(dep.fromId, dep.toId, { type: "SS", lagMs: 3600_000 });
  },
});

Dragging a grid row onto another fires onTaskReorder; the chart applies the reorder/reparent to its own internal task list immediately (live drag feedback) the same way column resize does. Double-clicking a dependency link fires onDependencyDblClick — the engine has no built-in "edit link" popover (keeping with zero-dependency, minimal-UI scope), so build your own and call chart.updateDependency(...) (undoable) to apply the result.

Double-clicking a task bar itself (not a link) fires the separate onTaskDblClick(task) — also off by default until you supply a handler.

Filling the viewport at any zoom level

A short project at month/year zoom, or a chart with just a handful of tasks, naturally renders narrow — the timeline's pixel width is purely a function of the task date range and the current zoom, with no awareness of how much horizontal space is actually available. Set autoFitToViewport: true to stretch (never shrink) the timeline so it fills the container instead of leaving empty space to the right:

new GanttChart(container, tasks, deps, { autoFitToViewport: true });

Re-evaluated on view-mode/task changes and on container resize (via ResizeObserver). It never applies if the content is already wider than the container — that's what normal horizontal scrolling is for. Off by default, so upgrading doesn't silently change an existing chart's pixel layout for anyone who hasn't opted in.

Separately, and always on: a short project at a coarse zoom no longer renders as one or two lonely ticks. The visible date range is padded out to a sensible minimum column count per zoom level (year: 5, quarter: 4, month/week: 6, day: 7, hour: 8), so a 3-month project at year zoom still shows a real multi-year timeline instead of a cramped sliver. Wider ranges are unaffected — this only ever pads, never trims.

The timeline's date header also stays pinned to the top of the chart while you scroll a tall task list vertically (it still scrolls horizontally in sync with the bars), instead of scrolling out of view along with the rows.

Multi-select and bulk actions

new GanttChart(container, tasks, deps, {
  selectable: true,
  onSelectionChange: (taskIds) => console.log("selected:", taskIds),
});

// Click selects one; ctrl/cmd-click toggles; shift-click selects a range (by visible row order).
chart.selectTask("t1");
chart.selectTask("t2", { additive: true });
chart.bulkShiftDates(24 * 60 * 60 * 1000); // shift every selected task by 1 day, one undo step
chart.bulkDelete(); // remove every selected task (and touching dependencies), one undo step

Split tasks

{ id: "qa", name: "QA pass", start, end, segments: [
  { start: day1, end: day2 },
  { start: day4, end: day5 }, // a gap, e.g. paused for an upstream fix
] }

start/end stay the authoritative overall envelope (used for dependencies, critical path, baseline, etc.); segments only changes how the bar is drawn — as separate rects joined by a dashed connector. Dragging the whole task shifts every segment together; resizing an edge only adjusts the first (left) or last (right) segment's outer edge, leaving any gap untouched.

Hover tooltips

Every bar shows a floating, CSS-customizable tooltip on hover (name, dates, progress, assignees, and task.notes if set) — not the browser's native title tooltip, so it's fully styleable:

.gantt-tooltip { background: #111827; border-radius: 8px; }
.gantt-tooltip-title { font-weight: 700; }

For full control over markup, renderTooltip works exactly like GanttColumn.render — return an HTMLElement to mount directly, a string (rendered as text, never parsed as HTML), or null/undefined to suppress the tooltip for that specific task:

new GanttChart(el, tasks, deps, {
  renderTooltip: (task) => {
    if (task.isMilestone) return null; // no tooltip on milestones
    const div = document.createElement("div");
    div.textContent = `${task.name} — ${task.progress ?? 0}%`;
    return div;
  },
});

Set showTooltip: false to disable it entirely.

i18n / overridable labels

renderTooltip and GanttColumn.render cover markup, but a handful of built-in strings (aria-labels, the default name for a newly-created task, the tooltip's date/progress formatting) are plain English by default. Override any subset of them via the labels option — every field is optional and additive:

new GanttChart(el, tasks, deps, {
  labels: {
    expand: "Développer",
    collapse: "Réduire",
    addChildTask: "Ajouter une sous-tâche",
    newTaskName: "Nouvelle tâche",
    formatDate: (d) => d.toLocaleDateString("fr-FR"),
    formatProgress: (p) => `${Math.round(p)} % terminé`,
    formatBarAriaLabel: (task, progress) =>
      `${task.name}, du ${task.start.toLocaleDateString("fr-FR")} au ${task.end.toLocaleDateString("fr-FR")}, ${progress} % terminé`,
  },
});

labels is reactive via setOptions({ labels: {...} }), same as any other option.

Custom timeline header tick labels

labels.formatTickLabel overrides the day/week/month/... column headings across the top of the timeline (distinct from labels.formatDate, which only covers the tooltip and bar aria-label). It receives each tick's real Date and the chart's current viewMode, so formatting can vary per zoom level:

new GanttChart(el, tasks, deps, {
  viewMode: "week",
  labels: {
    formatTickLabel: (date, viewMode) =>
      viewMode === "week"
        ? `W${Math.ceil(date.getDate() / 7)}`
        : date.toLocaleDateString("fr-FR"),
  },
});

Leave it unset to keep the built-in per-view-mode label (weekday+day at "day", month+year at "month", etc).

Custom sort comparator

GanttColumn.sortable sorts siblings by the column's accessor value by default. Set sortFn on a column to fully control the comparison instead — it receives the two raw tasks (not their accessor'd values); the chart still applies asc/desc direction on top of whatever it returns:

const columns: GanttColumn[] = [
  {
    id: "priority",
    title: "Priority",
    accessor: (t) => t.data?.priority,
    sortable: true,
    // "high" < "medium" < "low" instead of alphabetical
    sortFn: (a, b) => {
      const rank = { high: 0, medium: 1, low: 2 } as const;
      const ra = rank[a.data?.priority as keyof typeof rank] ?? 99;
      const rb = rank[b.data?.priority as keyof typeof rank] ?? 99;
      return ra - rb;
    },
  },
];

A column that leaves sortFn unset keeps the default accessor-based sort.

Custom task-bar rendering

renderBar lets you draw custom SVG content (icons, patterns, extra shapes) into a task bar without forking the renderer. It's called once per visible bar, after the default fill/progress/milestone/segment shapes are drawn but before the resize handles, link connector, and label are appended — append to (or restyle) ctx.group in place rather than replacing it, so drag/resize hit-testing (which depends on those handle/connector elements staying on top) keeps working:

new GanttChart(el, tasks, deps, {
  renderBar: ({ task, bar, group }) => {
    if (!task.data?.flagged) return;
    const icon = document.createElementNS("http://www.w3.org/2000/svg", "text");
    icon.setAttribute("x", String(bar.width + 6));
    icon.setAttribute("y", "-4");
    icon.textContent = "⚑";
    group.appendChild(icon);
  },
});

Leave renderBar unset for unchanged default rendering.

Task notes, auto-color, baseline history, and density presets

{ id: "t1", name: "Ship v2", start, end, notes: "Blocked on legal review" } // shown as an extra line in the default tooltip
import { applyColorByField } from "@ganttloom/gantt-core";

const colored = applyColorByField(tasks, (t) => t.assignees?.[0]?.id); // one color per first assignee
new GanttChart(container, colored, deps);
{ id: "t1", name: "Ship v2", start, end, baselines: [
  { label: "Original plan", start: d1, end: d2 },
  { label: "As of last Friday", start: d3, end: d4 },
] } // rendered as multiple faint stacked bars, oldest furthest back; requires showBaseline: true
import { DENSITY_PRESETS } from "@ganttloom/gantt-core";

new GanttChart(container, tasks, deps, { theme: { ...DENSITY_PRESETS.compact } });

None of these need a core option - notes/baselines are plain GanttTask fields, applyColorByField/DENSITY_PRESETS are pure helpers/data you apply before constructing the chart.

Partial-day working hours

const calendar: WorkingCalendar = {
  holidays: ["2026-01-19"],
  workingHours: { start: 9, end: 17 }, // 9am-5pm
};
new GanttChart(container, tasks, deps, { calendar, autoSchedule: true, viewMode: "hour" });

Narrows anchor points, not duration math: a 3-day task cascaded by autoSchedule lands its new start within the working window (not at 11pm), but stays a 3-calendar-day task, not "3 working-window days." Hour view shades individual non-working hours (not just whole non-working days). isWithinWorkingHours/isWorkingTime/shiftToWorkingTime are exported if you need the same logic elsewhere.

Resource leveling

const { tasks: leveled, shifted } = chart.getLeveledTasks();
// or standalone: levelResources(tasks, dependencies, { calendar })
chart.setTasks(leveled); // apply it
console.log(shifted); // [{ taskId, delayMs }, ...] - what moved and by how much

Delays (never pulls earlier) tasks so no assignee is double-booked on a working day, using the same "fully allocated while active" model as computeResourceHistogram - so a leveled schedule always shows zero overallocated buckets afterward. Respects FS ("finish-to-start") dependencies (never places a task before its FS predecessors finish); SS/FF/SF aren't specifically enforced - a documented scope limit rather than a full constraint solver.

Multi-page PNG export

const pages = await chart.toPNGDataURLs({ pageWidthPx: 1600, pageHeightPx: 1200 });
// [dataUrl1, dataUrl2, ...] in row-major tile order, each at full fidelity

For charts too large to comfortably rasterize as one image; omit the options to get a single image at the chart's actual size (same as toPNGDataURL()).

Keyboard navigation

With keyboardAccessible: true: Tab/Shift+Tab move between bars (native browser focus order - every bar has tabindex="0"), arrow keys nudge a focused bar by a day, Enter clicks it, Home/End scroll the timeline to the start/end of the project's date range, and Ctrl/Cmd+A selects every visible task when selectable is also on.

Zoom

chart.zoomIn();       // 1.25x by default, clamps at a 400px/unit ceiling
chart.zoomOut(1.5);   // custom factor
chart.getZoomLevel(); // current columnWidth, in px/unit
chart.fitToViewport(containerEl.clientWidth); // zoom-to-fit: scales so the whole project fits

Export / import

  • chart.toSVGString() / chart.toPNGDataURL() — raster/vector snapshot of the current render.
  • tasksToCSV(tasks) / tasksFromCSV(csv) — round-trip through CSV.
  • For PDF/PPTX, see @ganttloom/gantt-export, which consumes chart.getRenderModel() directly.

Theming

Every visual token is a --gantt-* CSS custom property (see styles.css), in addition to the JS theme option (Partial<GanttTheme>) — style via either, or both. colorScheme: "auto" (the default) follows prefers-color-scheme live.

To override a token via CSS, target .gantt-root itself (the element the chart renders into) rather than an ancestor — the defaults are declared on .gantt-root, and a same-element declaration always wins over one inherited from a parent, regardless of specificity:

.gantt-root {
  --gantt-bar-color: #16a34a;
  --gantt-critical-color: #ea580c;
}

Styling structural elements (buttons, cells, rows)

Beyond color tokens, every structural/interactive element the chart renders has a stable, undocumented-nowhere-else class name and no inline styles and no !important — so plain CSS targeting these classes has full control, same as any element you'd render yourself:

| Class | What it is | |---|---| | .gantt-toggle | The expand/collapse button on a group row | | .gantt-grid-header-cell | A column header cell | | .gantt-grid-cell | A grid body cell | | .gantt-grid-row | A grid body row (has a default :hover background) | | .gantt-rename-input | The inline text input shown while renaming a task | | .gantt-tooltip | The floating hover tooltip's container (see "Hover tooltips" above) | | .gantt-tooltip-title / .gantt-tooltip-row / .gantt-tooltip-notes | Elements inside the default tooltip content — irrelevant if you use renderTooltip |

For example, to make the expand/collapse toggle look like a small round button instead of bare text:

.gantt-toggle {
  border-radius: 999px;
  padding: 2px 6px;
  background: var(--gantt-grid-color, #e5e7eb);
}
.gantt-toggle:hover {
  background: var(--gantt-bar-color, #3b82f6);
  color: #fff;
}

None of this is specific to the library's own chrome, either — anything you render, like the playground demo's toolbar (apps/playground, apps/vue-playground), is plain HTML/CSS in your own app code with no library involvement at all, so it's exactly as customizable as any other element on your page.

Styling by state (data-attributes)

Render state that would otherwise need a JS hook to detect is also mirrored as data-* attributes on the relevant element, so plain CSS attribute selectors can theme it directly — no renderTooltip/GanttColumn.render required:

| Element | Attribute | Values | |---|---|---| | .gantt-bar (the <g> for a task bar) | data-gantt-critical | "true" / "false" | | | data-gantt-milestone | "true" / "false" | | | data-gantt-selected | "true" / "false" | | | data-gantt-overdue | "true" / "false" | | | data-gantt-group-bar | present ("true") only on a group/summary row's bar | | | data-gantt-readonly-bar | present ("true") only when the task is readonly | | .gantt-grid-row (a grid body row) | data-gantt-group-row | "true" / "false" | | | data-gantt-collapsed | "true" / "false" | | | data-gantt-depth | tree depth as a string, e.g. "0", "1" |

/* Give critical-path bars a dashed outline instead of relying on --gantt-critical-color alone */
.gantt-bar[data-gantt-critical="true"] rect {
  stroke-dasharray: 4 2;
}
/* Dim every selected bar's label */
.gantt-bar[data-gantt-selected="true"] text {
  font-style: italic;
}

Additional CSS variables

Beyond the GanttTheme color tokens, a few more structural choices are also --gantt-* custom properties on .gantt-root (with the same override pattern as above):

| Variable | Default | Controls | |---|---|---| | --gantt-radius | 8px | .gantt-root's corner radius | | --gantt-shadow | a soft drop shadow | .gantt-root's box-shadow | | --gantt-header-z-index | 2 | stacking order of the sticky grid/timeline header rows | | --gantt-tooltip-bg / --gantt-tooltip-color | dark background / light text | the default hover tooltip's colors | | --gantt-tooltip-radius / --gantt-tooltip-shadow / --gantt-tooltip-max-width / --gantt-tooltip-padding | 6px / soft shadow / 280px / 8px 10px | the default hover tooltip's box | | --gantt-tooltip-z-index | 1000 | the default hover tooltip's stacking order |

API surface

The full, current type/function list is re-exported from src/index.ts — treat it as the reference; this README covers the concepts, not every signature.