Vue ApexGantt
Installation
npm install vue-apexgantt apexgantt
# or
yarn add vue-apexgantt apexgantt
# or
pnpm add vue-apexgantt apexgantt
apexgantt is a peer dependency. This page describes the vue-apexgantt release built for apexgantt 3.20.0, which requires apexgantt 3.20.0 or later; notes mark what differs on earlier versions.
Demo
View Demo Project created using Vue-ApexGantt
License Setup
ApexGantt is included in the Premium plan and above, and there is no free tier. Without a key the chart renders with a trial watermark. Set your license key once at application start:
import { setApexGanttLicense } from 'vue-apexgantt';
// call this at the top of your app
setApexGanttLicense('your-license-key-here');
Example with Vue app entry point:
// main.ts
import { createApp } from 'vue';
import { setApexGanttLicense } from 'vue-apexgantt';
import App from './App.vue';
// set license before rendering
setApexGanttLicense('your-license-key-here');
createApp(App).mount('#app');
A key set later still removes the watermark from charts already on the page (apexgantt 3.17.0 and later); setting it first avoids a one-frame watermark. From apexgantt 3.20.0 the key's plan must be Premium or higher.
Quick Start
<template>
<ApexGanttChart
:tasks="tasks"
:pixels-per-day="25.7"
height="500px"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ApexGanttChart } from 'vue-apexgantt';
import type { TaskInput } from 'vue-apexgantt';
const tasks = ref<TaskInput[]>([
{
id: 'task-1',
name: 'Project Planning',
startTime: '01-01-2024',
endTime: '01-08-2024',
progress: 75,
},
{
id: 'task-2',
name: 'Development',
startTime: '01-09-2024',
endTime: '01-20-2024',
progress: 40,
dependency: 'task-1',
},
]);
</script>
API Reference
Component Props
| Prop | Type | Default | Description |
|---|---|---|---|
tasks | TaskInput[] | Required | Tasks to display. A change replaces the chart's data (see Prop changes and edits below) |
options | | | Any other ApexGantt option. Keys set here win over the matching props |
width | string | number | '100%' | Chart width. A number is read as pixels |
height | string | number | None | Chart height. A number is read as pixels. Leave it out to size the chart from CSS: with apexgantt 3.20.0 the chart fills a container that has a height of its own, and is 500px tall otherwise. Earlier releases of the component defaulted it to '500px', which overrode a height set in CSS |
pixelsPerDay | number | None | Timeline zoom in pixels-per-day; header tier auto-picks (0.5 year, 1.6 quarter, 4.9 month, 25.7 week, 80 day). Leave it out and the chart fits its zoom to the data span on first render. It is sent to the chart only when it changes to a number, so a zoom made from the toolbar survives other prop changes. Earlier releases of the component defaulted it to 4.9, so the chart never auto-fitted |
theme | 'light' | 'dark' | 'light' | Color theme |
className | string | '' | Class added to the chart container |
style | CSSProperties | | Inline styles added to the chart container |
Component Events
The component re-emits every ApexGantt event under the core's name, with the event's detail as the only argument. In a template, listen with the kebab-case form. Every detail type is exported from vue-apexgantt. Earlier releases emitted eight of these events (taskUpdate, taskUpdateSuccess, taskValidationError, taskUpdateError, taskDragged, taskResized, selectionChange, dependencyArrowUpdate).
| Event | Template listener | Payload Type | Fires when |
|---|---|---|---|
taskUpdate | @task-update | TaskUpdateEventDetail | An edit is being committed, before taskUpdateSuccess |
taskUpdateSuccess | @task-update-success | TaskUpdateSuccessEventDetail | An edit was applied |
taskUpdateError | @task-update-error | TaskUpdateErrorEventDetail | An edit failed to apply |
taskValidationError | @task-validation-error | TaskValidationErrorEventDetail | The task edit dialog failed validation |
taskAdded | @task-added | TaskAddedEventDetail | A task was inserted |
taskDeleted | @task-deleted | TaskDeletedEventDetail | A task was removed (once per task in a cascading delete) |
taskMoved | @task-moved | TaskMovedEventDetail | A task was moved to a new parent |
dependencyAdded | @dependency-added | DependencyChangeEventDetail | A dependency link was created, by addDependency() or drawn by the user |
dependencyRemoved | @dependency-removed | DependencyChangeEventDetail | A dependency link was removed, including once per link a deleted task takes with it |
taskDragged | @task-dragged | TaskDraggedEventDetail | A bar was dropped at a new date |
taskResized | @task-resized | TaskResizedEventDetail | A bar was resized by one of its handles |
taskProgressChanged | @task-progress-changed | TaskProgressChangedEventDetail | The in-bar progress handle was dragged to a new value |
selectionChange | @selection-change | SelectionChangeEventDetail | The selected rows changed (needs enableSelection) |
dependencyArrowUpdate | @dependency-arrow-update | DependencyArrowUpdateDetail | Internal redraw signal when a linked task moves; use dependencyAdded / dependencyRemoved for link changes |
historyChange | @history-change | HistoryChangeEventDetail | The undo/redo stack changed |
sortChange | @sort-change | SortChangeEventDetail | The active sort changed |
filterChange | @filter-change | FilterChangeEventDetail | The active filter changed |
groupChange | @group-change | GroupChangeEventDetail | The active grouping changed |
columnResize | @column-resize | ColumnResizeEventDetail | A task-list column was resized |
columnReorder | @column-reorder | ColumnReorderEventDetail | Task-list columns were reordered |
With apexgantt 3.20.0, taskUpdate and taskUpdateSuccess fire once per committed edit on every path (drag, resize, progress handle, dialog, inline edit, keyboard, updateTask()), with a source field naming the path, and taskDragged, taskResized and taskProgressChanged fire only after beforeTaskUpdate accepts the edit. An app that saves on both taskDragged and taskUpdateSuccess therefore saves each drag twice: save on taskUpdateSuccess alone. When a parent bar is dragged or moved with the keyboard, each moved task gets its own events, but all of the gesture's taskUpdate events arrive before its taskUpdateSuccess events.
Event Handling Example
<template>
<ApexGanttChart
:tasks="tasks"
@task-dragged="handleTaskDragged"
@task-resized="handleTaskResized"
@task-update-success="handleTaskUpdateSuccess"
/>
</template>
<script setup lang="ts">
import type {
TaskDraggedEventDetail,
TaskResizedEventDetail,
TaskUpdateSuccessEventDetail
} from 'vue-apexgantt';
const handleTaskDragged = (detail: TaskDraggedEventDetail) => {
console.log('Task dragged:', detail);
// save to backend, etc.
};
const handleTaskResized = (detail: TaskResizedEventDetail) => {
console.log('Task resized:', detail);
};
const handleTaskUpdateSuccess = (detail: TaskUpdateSuccessEventDetail) => {
console.log('Task updated:', detail);
};
</script>
Template Ref Methods
Access chart methods using template ref:
<template>
<ApexGanttChart
ref="ganttRef"
:tasks="tasks"
/>
<button @click="handleZoomIn">Zoom In</button>
<button @click="handleZoomOut">Zoom Out</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ApexGanttChart } from 'vue-apexgantt';
const ganttRef = ref<InstanceType<typeof ApexGanttChart> | null>(null);
const handleZoomIn = () => {
ganttRef.value?.zoomIn();
};
const handleZoomOut = () => {
ganttRef.value?.zoomOut();
};
// available methods:
// ganttRef.value?.update(options)
// ganttRef.value?.updateTask(taskId, data, options?)
// ganttRef.value?.zoomIn()
// ganttRef.value?.zoomOut()
// ganttRef.value?.destroy()
// ganttRef.value?.getInstance() // get raw ApexGantt instance
</script>
| Method | Description |
|---|---|
update(options) | Passes to the chart's update(). Leave out series and parsing to keep the chart's data, edits included (apexgantt 3.20.0) |
updateTask(taskId, data, options?) | Updates one task. skips taskUpdate and taskUpdateSuccess, for rollbacks and server echoes (apexgantt 3.20.0) |
zoomIn() / zoomOut() | Zoom by one step (the zoom is multiplied or divided by 1.4) |
destroy() | Removes the event listeners and destroys the chart. The component also does this when it unmounts |
getInstance() | The ApexGantt instance, or null before mount and after destroy() |
Every other core method is on getInstance(), for example scrollToTask, addTask, deleteTask, undo, redo, sort, filter, getState and exportChart, plus getTask, getTasks, getDependencies, expandTask and collapseTask from apexgantt 3.20.0:
ganttRef.value?.getInstance()?.scrollToTask('task-2');
Prop changes and edits
The component sends only what changed to the chart's update():
| Change | Sent to update() |
|---|---|
tasks (a new array, or a change inside it) | , which replaces the chart's data: edits made in the chart are discarded and undo history is cleared |
pixelsPerDay | |
theme | plus your options (without series, parsing and pixelsPerDay), so custom colours in your options survive the switch. From apexgantt 3.20.0 a theme change resets only the colours the theme owns and keeps every other option; on earlier releases it reset other options to the theme's defaults |
width, height | , |
options | Only the keys whose values changed. A key you remove falls back to the matching prop, or to the chart's default |
With apexgantt 3.20.0, an update that leaves out series and parsing keeps the edits made in the chart (dragging, resizing, the dialog, inline editing, the keyboard, updateTask, addTask, deleteTask, moveTask and link changes), and keeps a zoom made from the toolbar unless pixelsPerDay is part of it. To save the chart, save getInstance()?.getTasks() together with getInstance()?.getDependencies() (both apexgantt 3.20.0). A task's dependency holds only the link it was loaded with, so links added or drawn later are only in getDependencies(), which also reflects undo and redo; the dependencyAdded and dependencyRemoved events do not fire on undo or redo, so a link list kept from them drifts. See Saving links for reloading them. To save each edit as it happens, listen to taskUpdateSuccess, taskAdded, taskDeleted and taskMoved. Writing them back into the array bound to tasks resends the whole array as series, so change tasks when you mean to replace the data. Earlier releases resent series and the default pixelsPerDay on every prop change, so a theme toggle discarded edits and reset the zoom.
Composables
useGanttData
Parse external data into ApexGantt format:
<script setup lang="ts">
import { ref } from 'vue';
import { ApexGanttChart, useGanttData } from 'vue-apexgantt';
import type { ParsingConfig } from 'vue-apexgantt';
// raw API data
const apiData = ref([
{
task_id: 'T1',
task_name: 'Design Phase',
start_date: '01-01-2024',
end_date: '01-15-2024',
completion: 0.75,
},
// ...more tasks
]);
// parsing configuration
const parsingConfig: ParsingConfig = {
id: 'task_id',
name: 'task_name',
startTime: 'start_date',
endTime: 'end_date',
progress: {
key: 'completion',
transform: (value) => value * 100, // convert to percentage
},
};
// parse data
const tasks = useGanttData({
data: apiData,
parsing: parsingConfig,
});
</script>
<template>
<ApexGanttChart :tasks="tasks" />
</template>
Nested Objects & Transforms
Use dot notation for nested properties:
const nestedData = ref([
{
project: {
task: { id: 'T1', title: 'Design' },
dates: { start: '01-01-2024', end: '01-15-2024' },
status: { completion: 0.75 },
},
},
]);
const tasks = useGanttData({
data: nestedData,
parsing: {
id: 'project.task.id',
name: 'project.task.title',
startTime: 'project.dates.start',
endTime: 'project.dates.end',
progress: {
key: 'project.status.completion',
transform: (value) => value * 100,
},
},
});
Advanced Usage
Custom Options
<template>
<ApexGanttChart
:tasks="tasks"
:options="ganttOptions"
:pixels-per-day="25.7"
theme="dark"
/>
</template>
<script setup lang="ts">
import type { GanttUserOptions } from 'vue-apexgantt';
const ganttOptions: Partial<GanttUserOptions> = {
enableTaskDrag: true,
enableTaskResize: true,
enableTaskEdit: true,
barBackgroundColor: '#537CFA',
rowBackgroundColors: ['#FFFFFF', '#F8F9FA'],
// ...more options
};
</script>
Dynamic Zoom
<template>
<div>
<select v-model.number="pixelsPerDay">
<option :value="80">Day</option>
<option :value="25.7">Week</option>
<option :value="4.9">Month</option>
<option :value="1.6">Quarter</option>
<option :value="0.5">Year</option>
</select>
<ApexGanttChart
:tasks="tasks"
:pixels-per-day="pixelsPerDay"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const pixelsPerDay = ref(4.9);
</script>
Theme Switching
<template>
<div>
<button @click="toggleTheme">
{{ theme === 'light' ? 'Dark' : 'Light' }}
</button>
<ApexGanttChart
:tasks="tasks"
:theme="theme"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import type { ThemeMode } from 'vue-apexgantt';
const theme = ref<ThemeMode>('light');
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
};
</script>
Column Configuration
Customize the task list columns with columnConfig inside the options prop:
<template>
<ApexGanttChart :tasks="tasks" :options="ganttOptions" height="500px" />
</template>
<script setup lang="ts">
import type { GanttUserOptions } from 'vue-apexgantt';
const ganttOptions: Partial<GanttUserOptions> = {
columnConfig: [
{ key: 'name', title: 'Task Name', minWidth: '160px', flexGrow: 3 },
{ key: 'startTime', title: 'Start', minWidth: '100px', flexGrow: 1 },
{ key: 'endTime', title: 'End', minWidth: '100px', flexGrow: 1 },
{ key: 'progress', title: 'Progress', minWidth: '80px', flexGrow: 1 },
],
};
</script>
Annotations
Highlight specific dates or date ranges with visual annotations:
<template>
<ApexGanttChart :tasks="tasks" :options="ganttOptions" height="500px" />
</template>
<script setup lang="ts">
import type { GanttUserOptions } from 'vue-apexgantt';
const ganttOptions: Partial<GanttUserOptions> = {
annotations: [
{
x1: '03-15-2024', // parsed with inputDateFormat, 'MM-DD-YYYY' by default
x2: '03-31-2024',
label: {
text: 'Sprint 1',
fontColor: '#333333',
},
},
{
x1: '04-01-2024',
label: {
text: 'Go Live',
fontColor: '#008FFB',
},
},
],
};
</script>
TypeScript Support
The package includes full TypeScript definitions. Import types as needed:
import type {
TaskInput,
ThemeMode,
GanttUserOptions,
ParsingConfig,
TaskUpdateEventDetail,
TaskDraggedEventDetail,
TaskResizedEventDetail,
HistoryChangeEventDetail,
// ...and the detail type of every other event
} from 'vue-apexgantt';
TaskType, ColumnKey, GanttEvents, LightTheme, DarkTheme and DataParser are exported as values for runtime use (import { TaskType } from 'vue-apexgantt').