Using plugins
Every plugin has one contract: { name, install(ctx) }. You install a renderer (which paints) plus any feature plugins (which shape data or scene). Install order matters in two cases, noted below.
Some factories return the plugin directly engine.use(progressPlugin()). Others return a controller with imperative methods, and you install its .plugin engine.use(createTree().plugin), then call tree.expandAll(). Each section says which.
Renderers
A renderer is a plugin that subscribes to scene:change and paints. Install exactly one.
Maps each primitive to an SVG element. The reference renderer.
Maps each primitive to a positioned <div>.
Draws primitives to a 2D canvas. Best for very large datasets.
svgRenderer(options)
| Option | Type | Default | Notes |
|---|---|---|---|
target | string | HTMLElement | required | Selector or element to mount into. Gets the .gantt class. |
theme | 'dark' | 'light' | 'light' | Sets the data-theme attribute. |
enableZoom | boolean | true | Ctrl/⌘+wheel steps the view mode. |
enablePan | boolean | true | Drag empty space to pan the chart. |
import { GanttEngine } from '@ganttkit/core'
import { svgRenderer } from '@ganttkit/svg'
import '@ganttkit/svg/styles.css'
const engine = new GanttEngine({ rows })
engine.use(svgRenderer({ target: '#chart', theme: 'dark' }))createGantt(options) is the same thing in one call it forwards target, theme, enableZoom, enablePan to the renderer and the rest to the engine. The html and canvas renderers expose the same option shape.
plugin-columns
controller · .plugin provides gantt:sidebar
Adds the sidebar. The core has no sidebar of its own a renderer draws one only when this service is present. Columns read a row field, a task field, or a formatter.
| Option | Type | Default | Notes |
|---|---|---|---|
columns | Column[] | [{key:'name'}] | Column defs (see below). |
sidebarWidth | number | 200 | Total sidebar width; unspecified column widths split the remainder. |
indentPerLevel | number | 16 | Pixels of indent per tree level. |
A Column is { key, label, width?, formatter?(row) => string }. Value resolution: the row's own field, else the first task's field, else a formatter override.
import { createColumns } from '@ganttkit/plugin-columns'
const columns = createColumns({
sidebarWidth: 240,
columns: [
{ key: 'id', label: 'Code', width: 90 },
{ key: 'name', label: 'Task' },
{ key: 'owner', label: 'Owner', formatter: (row) => row.owner ?? '' },
],
})
engine.use(columns.plugin)
// imperative
columns.setColumns([{ key: 'name', label: 'Name' }])
columns.setSidebarWidth(300)plugin-tree
controller · .plugin taps hooks.rows
Turns a flat row list with parentId into a collapsible hierarchy. It computes each row's level and hasChildren, hides descendants of collapsed rows, and reacts to the renderer's row:toggle event (the sidebar chevron).
import { createTree } from '@ganttkit/plugin-tree'
const tree = createTree({ collapsed: ['archived'] })
engine.use(tree.plugin)
tree.toggle('platform')
tree.expandAll()
tree.collapseAll()
tree.isCollapsed('platform') // booleanMethods: toggle, expand, collapse, expandAll, collapseAll, isCollapsed. Commands: tree.toggle, tree.expand, tree.collapse, tree.expandAll, tree.collapseAll.
plugin-filter
controller · .plugin taps hooks.rows
Filters rows and tasks through the data pipeline with composable predicates. By default, rows left with no tasks are dropped.
import { createFilter, filters } from '@ganttkit/plugin-filter'
const filter = createFilter()
engine.use(filter.plugin)
// wire a search box to the built-in task-name predicate
searchInput.addEventListener('input', (e) => {
const q = e.target.value.trim()
filter.setTaskFilter(q ? filters.taskNameIncludes(q) : null)
})
// custom predicates: (task, row) => boolean / (row) => boolean
filter.setRowFilter((row) => row.level === 0 || row.tasks.length > 0)Methods: setRowFilter, setTaskFilter, clear. Helpers in filters: taskNameIncludes(q), rowNameIncludes(q), taskOverlaps(from, to). Commands: filter.setRowFilter, filter.setTaskFilter, filter.clear.
plugin-progress
plugin taps hooks.scene
Draws a completion fill inside each bar from task.progress (0–1). Milestones and tasks with no progress are skipped. Adds a progress layer after bars.
import { progressPlugin } from '@ganttkit/plugin-progress'
engine.use(progressPlugin()) // reads task.progress
engine.use(progressPlugin({ className: 'my-fill' }))plugin-markers
controller · .plugin taps hooks.scene
Vertical date markers and shaded bands a today line, deadlines, sprint ranges. A marker with an end renders as a band; without one, as a line. Adds a markers layer after grid.
A Marker is { id?, date, end?, label?, className? }.
import { createMarkers, todayMarker } from '@ganttkit/plugin-markers'
const markers = createMarkers([
todayMarker({ label: 'Today' }),
{ id: 'sprint', date: '2026-07-06', end: '2026-07-19', label: 'Sprint 14' },
{ id: 'ga', date: '2026-08-31', label: 'GA', className: 'is-deadline' },
])
engine.use(markers.plugin)
markers.addMarker({ id: 'freeze', date: '2026-08-15', label: 'Code freeze' })
markers.removeMarker('sprint')
markers.clearMarkers()plugin-dependencies
controller · .plugin taps hooks.scene overlay slot
Finish-to-start scheduling. When autoSchedule is on, moving a task shifts its dependents to stay after it. When linkDrag is on, each bar gets a connector handle drag from one bar to another to create a link. Cycles are rejected.
| Option | Type | Default | Notes |
|---|---|---|---|
gap | number | 0 | Extra days enforced between a predecessor's end and a successor's start. |
autoSchedule | boolean | true | Reschedule dependents on task:dragend. |
linkDrag | boolean | true | Draw connector handles and enable drag-to-link. |
import { createDependencies } from '@ganttkit/plugin-dependencies'
const deps = createDependencies({ autoSchedule: true, gap: 1 })
engine.use(deps.plugin)
deps.addDependency('a1', 'a2') // predecessor, successor → false if it would create a cycle
deps.removeDependency('a1', 'a2')
const shifted = deps.reschedule() // returns how many tasks movedplugin-baseline
controller · .plugin taps hooks.scene
Planned-vs-actual ghost bars. Capture the current dates as a baseline, then edits render against the ghosts. Adds a baseline layer before bars.
import { createBaseline } from '@ganttkit/plugin-baseline'
const baseline = createBaseline()
engine.use(baseline.plugin)
baseline.capture() // snapshot every task's current start/end
baseline.clear()
baseline.setBaseline({ a1: { start: '2026-07-01', end: '2026-07-10' } })
baseline.get() // the current baseline mapplugin-toolbar
plugin toolbar slot
A toolbar of view-mode buttons, zoom, and jump-to-today, contributed to the toolbar UI slot so it works on any renderer. It can also surface tree and baseline actions, and localizes its labels through gantt:i18n when present.
| Option | Default | Notes |
|---|---|---|
viewModes | ['Day','Week','Month'] | Which mode buttons to show. |
zoom | true | Show +/− zoom (steps the view mode). |
today | true | Show a "Today" scroll button. |
expandCollapse | false | Expand/collapse-all buttons (calls tree.* commands). |
baseline | false | Capture/clear-baseline buttons (calls baseline.* commands). |
import { toolbarPlugin } from '@ganttkit/plugin-toolbar'
engine.use(toolbarPlugin({ expandCollapse: true, baseline: true }))plugin-tooltip
plugin overlay slot
A hover card with the task name, dates and completion. Dates format with the active i18n locale. Pass content to render your own card (string or DOM node).
import { tooltipPlugin } from '@ganttkit/plugin-tooltip'
engine.use(tooltipPlugin())
engine.use(tooltipPlugin({
content: (task) => `<b>${task.name}</b><br>${task.owner ?? ''}`,
}))plugin-selection
controller · .plugin taps hooks.scene overlay slot
Click to select; Ctrl/⌘/Shift+click to multi-select; shift-drag for a rubber-band; right-click for a context menu. Selected bars get an outline via a selection layer. Uses the gantt:viewport service to hit-test.
import { createSelection } from '@ganttkit/plugin-selection'
const selection = createSelection({
multi: true,
rubberBand: true,
menu: [
{ label: 'Log selection', action: (ids) => console.log(ids) },
{ label: 'Clear', action: () => selection.clear() },
],
})
engine.use(selection.plugin)
selection.select(['a1', 'a2'])
selection.toggle('a3')
selection.getSelected() // string[]plugin-i18n
controller · .plugin provides gantt:i18n
Localized timeline dates plus translatable UI strings. It swaps in an Intl date adapter for the locale and publishes a t() service the toolbar and tooltip consume. Locale switching is runtime, with subtag fallback (fr-CA → fr → en).
import { createI18n } from '@ganttkit/plugin-i18n'
const i18n = createI18n({
locale: 'fr',
fallbackLocale: 'en',
messages: {
fr: {
'view.Day': 'Jour', 'view.Week': 'Semaine', 'view.Month': 'Mois',
'toolbar.today': "Aujourd'hui",
'tooltip.complete': '{percent}% terminé',
},
},
})
engine.use(i18n.plugin) // install FIRST see below
i18n.setLocale('de') // re-localizes dates + relabels the toolbar, no reload
i18n.t('toolbar.today')Install i18n before the toolbar and tooltip. They look up the gantt:i18n service at mount time; if it isn't there yet they fall back to English and won't relabel on locale change.
Composing a full chart
The demo installs everything. Note the order: renderer, then i18n, then the UI plugins that depend on it.
const engine = new GanttEngine({ rows, viewMode: 'Month', highlightToday: true })
engine.use(svgRenderer({ target: '#chart', theme: 'dark' }))
const i18n = createI18n({ locale: 'en', messages })
engine.use(i18n.plugin) // 1. service first
engine.use(toolbarPlugin({ expandCollapse: true, baseline: true }))
engine.use(tooltipPlugin())
engine.use(createColumns({ sidebarWidth: 220, columns: [
{ key: 'id', label: 'Code', width: 120 }, { key: 'name', label: 'Name' },
]}).plugin)
engine.use(progressPlugin())
engine.use(createMarkers([todayMarker({ label: 'Today' })]).plugin)
engine.use(createTree().plugin)
engine.use(createDependencies({ autoSchedule: true }).plugin)
engine.use(createBaseline().plugin)
engine.use(createFilter().plugin)
engine.use(createSelection({ menu: [/* … */] }).plugin)Want to build your own? Head to Writing a plugin.