Introduction / Getting started

Getting started

Install a renderer plus the plugins you want, compose them, and mount. There are no config files and no global registration a chart is a GanttEngine with renderer and feature plugins installed on it.

Install

Pick a renderer and add it alongside the core. Everything is published under the @ganttkit scope.

# vanilla SVG renderer + core
pnpm add @ganttkit/core @ganttkit/svg

# or the HTML renderer
pnpm add @ganttkit/core @ganttkit/html

# or a framework binding, which brings the HTML renderer with it
pnpm add @ganttkit/core @ganttkit/vue

# feature plugins are separate packages - add only what you use
pnpm add @ganttkit/plugin-columns @ganttkit/plugin-tree @ganttkit/plugin-progress

The core has zero runtime dependencies and every package ships ESM + CJS with types. Plugins are tree-shakeable, so an unused plugin costs nothing in your bundle.

Your first chart

createGantt from @ganttkit/svg is the fastest path: it builds a GanttEngine and installs the SVG renderer for you.

import { createGantt } from '@ganttkit/svg'
import '@ganttkit/svg/styles.css'

const gantt = createGantt({
  target: '#chart',      // a selector or an HTMLElement
  theme: 'dark',         // 'dark' | 'light'
  viewMode: 'Week',      // 'Day' | 'Week' | 'Month'
  rows: [
    {
      id: 'design',
      name: 'Design',
      tasks: [
        { id: 't1', name: 'Wireframes', start: '2026-07-01', end: '2026-07-08', progress: 1 },
        { id: 't2', name: 'Mockups',    start: '2026-07-09', end: '2026-07-20', progress: 0.4 },
      ],
    },
    {
      id: 'build',
      name: 'Development',
      tasks: [
        { id: 't3', name: 'API',      start: '2026-07-15', end: '2026-08-05', progress: 0.2 },
        { id: 'ms', name: 'Launch',   start: '2026-08-31', end: '2026-08-31', kind: 'milestone' },
      ],
    },
  ],
})

// later…
gantt.setViewMode('Month')
gantt.destroy()

The target element only needs a size the renderer fills it and scrolls internally:

<div id="chart" style="height: 480px"></div>

The explicit form

createGantt is sugar. When you want to add feature plugins, build the engine yourself and use() each plugin. The engine is chainable and recomputes after every install.

import { GanttEngine } from '@ganttkit/core'
import { svgRenderer } from '@ganttkit/svg'
import { createColumns } from '@ganttkit/plugin-columns'
import { progressPlugin } from '@ganttkit/plugin-progress'
import '@ganttkit/svg/styles.css'

const engine = new GanttEngine({ rows, viewMode: 'Week' })

engine.use(svgRenderer({ target: '#chart', theme: 'dark' }))
engine.use(createColumns({ columns: [{ key: 'name', label: 'Task' }] }).plugin)
engine.use(progressPlugin())

Some plugins return a controller object whose .plugin you install (createColumns(…).plugin) the controller also carries imperative methods. Others return the plugin directly (progressPlugin()). The Using plugins page notes which is which.

With a framework

@ganttkit/vue, @ganttkit/react and @ganttkit/angular are bindings, not extra renderers. The engine still computes the scene and a base renderer still paints it; the binding only owns that renderer's lifetime and maps the options onto the framework's reactivity, so every feature plugin works unchanged.

<script setup>
import { GanttChart } from '@ganttkit/vue'
import { createColumns } from '@ganttkit/plugin-columns'
import { progressPlugin } from '@ganttkit/plugin-progress'
import '@ganttkit/vue/styles.css'

const rows = [ /*  */ ]
const plugins = [
  createColumns({ columns: [{ key: 'name', label: 'Task' }] }).plugin,
  progressPlugin(),
]
</script>

<template>
  <GanttChart :rows="rows" :plugins="plugins" view-mode="Week" theme="dark" />
</template>

Choosing a renderer

The three base renderers consume the identical scene, so they are drop-in swaps for one another. A vanilla caller picks one by importing from that package. A binding takes it as an option instead, and defaults to @ganttkit/html:

import { GanttChart } from '@ganttkit/vue'
import { canvasRenderer } from '@ganttkit/canvas'
import '@ganttkit/canvas/styles.css'

// <GanttChart :renderer="canvasRenderer" :rows="rows" />
RendererPackageStylesheetPaints each primitive as
htmlRenderer default@ganttkit/html@ganttkit/vue/styles.cssa positioned <div>
svgRenderer@ganttkit/svg@ganttkit/svg/styles.cssan SVG element
canvasRenderer@ganttkit/canvas@ganttkit/canvas/styles.cssa draw call on one 2D canvas
!

Import the stylesheet that matches the renderer you chose. A binding's own styles.css re-exports the HTML sheet only, because that is the renderer it depends on; @ganttkit/svg and @ganttkit/canvas are optional peers, so import their CSS from the same package you import the renderer from.

The contract is a plugin factory - RendererFactory in @ganttkit/core - so a custom renderer is accepted everywhere the shipped three are.

The data model

A chart is an array of rows; each row owns an array of tasks. Dates are anything the date adapter can parse an ISO string ('2026-07-01') or a Date.

Row

FieldTypeNotes
idstringRequired. Unique across all rows.
namestringDisplay label (also the default sidebar column).
tasksTask[]Required. May be empty (e.g. a group header row).
levelnumberoptional Indent depth. Set by plugin-tree if omitted.
parentIdstringoptional Parent row id drives the tree hierarchy.
hasChildrenbooleanoptional Renders a collapse chevron in the sidebar.
expandedbooleanoptional Initial expand state for a parent row.
[key: string]unknownExtra fields are readable by column formatters.

Task

FieldTypeNotes
idstringRequired. Unique across all tasks (dependencies reference it).
namestringRendered as the bar label.
startstring | DateRequired. Parsed by the date adapter.
endstring | DateRequired. Inclusive a same-day start/end is one day wide.
kind'task' | 'milestone'optional A milestone renders as a diamond at end.
progressnumberoptional 0–1. Drawn by plugin-progress.
dependenciesstring[]optional Predecessor task ids (finish-to-start).
classNamestringoptional Added to the bar for custom styling.
draggablebooleanoptional Per-task override of the engine draggable option.
tooltipstringoptional Native SVG <title> text.
const rows = [
  {
    id: 'platform', name: 'Platform', level: 0, hasChildren: true, expanded: true, tasks: [],
  },
  {
    id: 'api', name: 'API', level: 1, parentId: 'platform',
    tasks: [
      { id: 'a1', name: 'Schema',  start: '2026-07-01', end: '2026-07-10', progress: 1,  className: 'task-completed' },
      { id: 'a2', name: 'Gateway', start: '2026-07-11', end: '2026-07-28', progress: 0.3, dependencies: ['a1'] },
    ],
  },
]

Theming

The renderer adds the .gantt class and a data-theme attribute to your target element and ships a stylesheet of CSS custom properties. Override any --gk-* variable to re-brand this is exactly how the demo applies the orange palette.

/* load the renderer theme first, then override */
@import '@ganttkit/svg/styles.css';

.gantt[data-theme='dark'] {
  --gk-accent:       #f0a030;
  --gk-bar:          rgba(240,160,48,.42);
  --gk-bar-stroke:   #f0a030;
  --gk-today-bg:     rgba(240,160,48,.13);
  --gk-progress:     rgba(61,214,140,.55);
  --gk-milestone:    #f0a030;
}

/* task classNames become bar modifiers */
.gantt .gantt-bar.task-completed { fill: rgba(61,214,140,.5); stroke: #3dd68c; }

Switch themes at runtime by toggling the attribute on the same element:

const el = document.querySelector('#chart')
el.setAttribute('data-theme', el.getAttribute('data-theme') === 'dark' ? 'light' : 'dark')
!

Load the renderer's styles.css before your overrides. Both target .gantt[data-theme=…] at equal specificity, so source order decides the winner.

Framework notes

  • SSR. The core never touches the DOM, so it computes fine on the server. Renderers mount on the client.
  • Vue / React / Angular. Official bindings ship as @ganttkit/vue, @ganttkit/react and @ganttkit/angular - see With a framework above.
  • Other frameworks. A renderer is a thin plugin - see Writing a plugin and the svgRenderer source for the pattern.
  • Cleanup. Always call engine.destroy() (or unmount the Vue component) to remove listeners and dispose plugins in reverse order.