Framework-agnostic · Plugin-driven · Vector-native

The Gantt engine your
team actually ships with

A headless core emits a declarative scene of Vector primitives. Renderers paint it. Plugins shape it. Nothing is glued to a framework.

$ pnpm add @ganttkit/core @ganttkit/svg
▶ Launch live demo
@ganttkit/svg · click to replay rendering
0
core runtime dependencies
11
official feature plugins
~80
Vector nodes at 20 000 tasks
<1ms
viewport rebuild on scroll
Architecture

One engine. Any renderer.
Every feature is a plugin.

The engine outputs a pure data structure a list of Vector primitives. It never touches the DOM. Renderers map primitives to elements; plugins hook the data pipeline and the scene.

Built for scale

Viewport virtualization windows the scene to what's visible. A 20 000-task chart emits ~80 Vector nodes instead of ~90 000. Scroll rebuilds run under a millisecond two passes keep cost bounded regardless of dataset size.

🔌

Truly composable plugins

Each plugin receives a context with store, events, commands, services, ui, and hooks. The sidebar, tree, toolbar, and i18n are all plugins nothing is hardwired into the engine.

🧪

Pure logic, fully testable

The engine runs in Node no DOM, no browser globals required. Layout, time-scale, scene generation, and plugin behaviour are all unit-testable without a browser. 83 tests, all green.

🌐

Framework-agnostic by design

Geometry is computed once in core; renderers map it to elements. Adding a React or Svelte renderer is a thin adapter, not a fork. All 11 feature plugins work across SVG, HTML, and Canvas unchanged.

Core

@ganttkit/core

Owns rows, time-scale, layout geometry, drag state, and the plugin host. Zero DOM, zero framework. Emits a declarative Scene through hooks and events.

Feature plugins

columns · tree · i18n · …

Hook hooks.rows to transform data, or hooks.scene to add Vector layers. Publish capabilities via the service registry.

Renderer

SVG · HTML · Canvas

Subscribes to scene:change, maps primitives to Vector, forwards pointer events back to the engine. Hosts UI slots for plugin-contributed DOM.

Plugins

Install only what you need

Each plugin is a separate package with a single contract: { name, install(ctx) }. No hidden coupling, no global side effects.

Renderers
@ganttkit/svg

Plain SVG display. No framework dependency. Works in any web environment.

@ganttkit/html

Plain HTML/CSS. No framework dependency. Works in any web environment.

@ganttkit/canvas

Canvas renderer with high performance drawing, suitable for large datasets.

Feature plugins
@ganttkit/plugin-columns

Sidebar column definitions. Not in core renderers draw a sidebar only when installed.

@ganttkit/plugin-tree

Hierarchical rows with expand / collapse. Commands: toggle, expandAll, collapseAll.

@ganttkit/plugin-filter

Filter and sort rows via the data pipeline. Composable predicates, zero boilerplate.

@ganttkit/plugin-progress

Completion fill inside task bars, reading task.progress (0–1).

@ganttkit/plugin-markers

Vertical date markers and bands today line, deadlines, sprint ranges.

@ganttkit/plugin-dependencies

Finish-to-start auto-scheduling. Drag a task → dependents shift. Drag a connector → new link.

@ganttkit/plugin-scheduler

Resource↔task assignment scheduling. Drag-to-assign between resource lanes and tasks. Availability calendars with working days and blackout dates.

@ganttkit/plugin-baseline

Planned-vs-actual ghost bars. baseline.capture() snapshots, baseline.clear() resets.

@ganttkit/plugin-toolbar

View-mode selector, zoom, jump-to-today. Contributed to the toolbar UI slot one package, any renderer.

@ganttkit/plugin-tooltip

Hover detail card with task name, dates, and completion. Locale-aware formatting.

@ganttkit/plugin-selection

Click-select, shift-drag rubber-band, and context menu. Custom actions, multi-select.

@ganttkit/plugin-i18n

Locale-aware timeline + translatable strings. Runtime switching, no reload, subtag fallback.

Performance

20 000 tasks.
~80 Vector nodes. Sub-millisecond scroll.

Two-pass compute keeps cost bounded. The heavy pass runs on data change; the cheap pass runs on scroll. The renderer always sees a windowed scene not the full dataset.

Scene nodes naive render~90 000
Scene nodes virtualized~80
Scroll rebuild (rebuildScene)< 1 ms
Full recompute at 20k tasks~8 ms

Two-pass compute

recompute() runs the full pipeline on data change. rebuildScene(reason) runs a cheap windowed pass on scroll never re-invoking row hooks.

O(vis)

Viewport-bounded scene

Only rows and day-columns inside the scroll viewport are emitted, plus a configurable overscan. Canvas stays full-size; only Vector children change.

rAF

RAF-coalesced scroll

Scroll events are debounced to the animation frame. Multiple fast scrolls merge into one rebuild no stale work queued.

// HTML + compose plugins, instantiate renderer
import { createGantt }      from '@ganttkit/html'
import { createColumns }    from '@ganttkit/plugin-columns'
import { progressPlugin }   from '@ganttkit/plugin-progress'
import { createTree }       from '@ganttkit/plugin-tree'
import { toolbarPlugin }    from '@ganttkit/plugin-toolbar'

const rows = [
  { id: '1', name: 'Design', start: '2026-07-01', end: '2026-07-15' },
  { id: '2', name: 'Development', start: '2026-07-10', end: '2026-08-05' },
]

const plugins = [
  progressPlugin(),
  toolbarPlugin(),
  createColumns({ columns: [{ key: 'name', label: 'Task' }] }).plugin,
  createTree().plugin,
]

const gantt = createGantt({
  target: '#gantt',
  rows,
  plugins,
  startDate: '2026-07-01',
  endDate: '2026-08-31',
})
Quick start

Drop in, wire up, ship

Compose your task rows and plugins, instantiate with createGantt, and point it at a DOM target. No config files, no framework lock-in.

The same plugin array works across @ganttkit/svg, @ganttkit/html, and @ganttkit/canvas just swap the import.

ESM + CJS TypeScript SSR-safe Tree-shakeable MIT