@fdecourt/vue-ganttastic
Vue Ganttastic
Vue Ganttastic provides editable Vue 3 Gantt components plus a framework-independent, immutable timeline core.
Features
- Automatic sub-lanes for overlapping bars, with stable visual positions during editing.
- Mouse, touch and keyboard move/resize interactions.
- Minute, hour, day, week or month scales and per-item snapping.
- Exact elapsed-duration preservation when moving whole bars.
- Duration bars, grouped bundles and point-in-time diamond or teardrop milestones.
- Optional top or bottom timeline navigator with interval overview and accessible range zoom.
- Cross-row moves, finish-to-start dependencies and collision policies.
- Custom property mappings and functional adapters for arbitrary domain models.
- Scoped slots for bars, row labels, tooltips and time-axis units.
- IANA time zones, locale-aware labels and configurable week boundaries.
- Locale-aware weekends, explicit closures and lazy country holidays at day precision.
- A zoom-aware real-current-time line spanning the dynamic row area.
- Responsive rendering, visible-range culling and a generic virtual-list component.
- ESM, CommonJS, CSS and bundled TypeScript declarations.
Requirements
- Node.js 20.19 or newer for package installation.
- Vue 3.5.41 or newer in the consuming application.
Vue is a peer dependency and is never bundled with the library. Day.js is installed as the only runtime dependency.
Installation
Published package:
npm install @fdecourt/vue-ganttastic
Register the plugin once and import the stylesheet explicitly:
import { createApp } from "vue"
import App from "./App.vue"
import ganttastic from "@fdecourt/vue-ganttastic"
import "@fdecourt/vue-ganttastic/style.css"
createApp(App).use(ganttastic).mount("#app")
Components may also be imported individually:
import {
GGanttChart,
GGanttRow,
GGanttVirtualList
} from "@fdecourt/vue-ganttastic"
Complete example
<script setup lang="ts">
import { ref } from "vue"
import type { GanttBarObject } from "@fdecourt/vue-ganttastic"
const analysis = ref<GanttBarObject[]>([
{
start: "2026-07-06 00:00",
end: "2026-07-10 00:00",
ganttBarConfig: {
id: "analysis",
label: "Analysis",
hasHandles: true,
snapTo: "day"
}
},
{
start: "2026-07-08 00:00",
end: "2026-07-14 00:00",
ganttBarConfig: {
id: "research",
label: "Research",
snapTo: "day"
}
}
])
const delivery = ref<GanttBarObject[]>([
{
start: "2026-07-13 00:00",
end: "2026-07-24 00:00",
ganttBarConfig: {
id: "implementation",
label: "Implementation",
hasHandles: true,
dependsOn: "analysis",
snapTo: "day"
}
},
{
start: "2026-07-31 16:30",
ganttBarConfig: {
id: "release",
label: "Release",
milestone: "diamond"
}
}
])
const selectedTaskId = ref<string | null>(null)
function addTask() {
delivery.value.push({
start: "2026-08-03 00:00",
end: "2026-08-07 00:00",
ganttBarConfig: {
id: crypto.randomUUID(),
label: "New task",
hasHandles: true,
snapTo: "day"
}
})
}
function removeTask(id: string) {
analysis.value = analysis.value.filter(bar => bar.ganttBarConfig?.id !== id)
delivery.value = delivery.value.filter(bar => bar.ganttBarConfig?.id !== id)
selectedTaskId.value = null
}
function selectTask(bar: GanttBarObject) {
selectedTaskId.value = bar.ganttBarConfig?.id ?? null
}
</script>
<template>
<button type="button" @click="addTask">Add task</button>
<button
type="button"
:disabled="!selectedTaskId"
@click="selectedTaskId && removeTask(selectedTaskId)"
>
Remove selected task
</button>
<GGanttChart
chart-start="2026-07-01 00:00"
chart-end="2026-08-31 23:59"
precision="week"
zoom
zoom-position="top"
snap-to="day"
time-zone="Europe/Paris"
locale="en"
overlap-layout="stack"
allow-row-drag
grid
bar-start="start"
bar-end="end"
@click-bar="selectTask($event.bar)"
>
<GGanttRow row-id="analysis" label="Analysis" v-model:bars="analysis">
<template #bar="{ config }">
<span>{{ config.label }}</span>
</template>
</GGanttRow>
<GGanttRow row-id="delivery" label="Delivery" v-model:bars="delivery" />
</GGanttChart>
</template>
precision controls the displayed axis. snap-to controls interactions independently. A bar can
override the chart with ganttBarConfig.snapTo and snapStep.
Timeline navigator and zoom
Enable zoom to display a compact navigator over the full chart-start / chart-end domain. Its
overview is the union of every bar and milestone interval: activity is continuous where events
touch or overlap and split into segments where the timeline is empty.
<GGanttChart
chart-start="2026-01-01 00:00"
chart-end="2026-12-31 23:59"
precision="month"
zoom
zoom-position="bottom"
@zoom-change="visibleRange = $event"
@zoom-reset="visibleRange = $event"
>
<!-- rows -->
</GGanttChart>
Drag either handle to resize the visible range or drag the selected window to move it. The native
range handles support keyboard and touch input. Double-click the selection or use Reset to
restore the full domain. zoom-position accepts top (default) or bottom.
The axis precision adapts to the visible duration and available width from month down to minute.
Pass :zoom-auto-precision="false" to keep the configured precision. Both zoom events expose
{ start, end, originalEvent }; start and end follow date-format and are native Date values
when :date-format="false".
Lower day and week units display only their number by default because the month is already shown
above. Add timeaxis-unit-include-month to repeat the localized short month when desired. Month
names follow locale; import the corresponding Day.js locale once before selecting it:
import "dayjs/locale/fr.js"
<GGanttChart locale="fr" timeaxis-unit-include-month ... />
Calendar and visual labels
Weekend columns receive a subtle full-height background at day precision. This follows the
regional weekend returned for locale by the browser and is enabled by default; disable it with
:highlight-weekends="false".
Add project closures through non-working-dates, or set holiday-country to an ISO 3166-1
alpha-2 code such as FR. National public holidays are loaded through date-holidays only when
the country option is used. Both sources reuse the daily background layer.
<GGanttChart
holiday-country="FR"
:non-working-dates="['2026-05-15']"
...
/>
Enable bar-label-overflow when complete bar labels may extend beyond short bars. Their actual
rendered size is then measured and bars and milestones share collision-free sub-lines, including
with overlap-layout="overlay". Use hide-label-column-title to preserve the label column while
leaving its top cell blank and transparent.
<GGanttChart
locale="fr"
bar-label-overflow
hide-label-column-title
...
/>
Current-time line
The user's real current instant is shown by default as a continuous red 3-pixel line. It begins below the time axis, spans the complete dynamic row height and follows navigator zoom. It is hidden outside the visible range.
<GGanttChart current-time-color="#dc2626" :current-time-width="2" ... />
Set :current-time="false" to disable it. current-time-label and current-time-refresh remain
available.
Data model
By default, bar-start, bar-end and bar-config-key select properties on each item. Metadata must
contain a chart-wide unique string id:
interface Task {
start: string | Date
end?: string | Date // omitted for milestones
ganttBarConfig: {
id: string
label?: string
hasHandles?: boolean
immobile?: boolean
bundle?: string
milestone?: "diamond" | "teardrop"
dependsOn?: string | string[]
snapTo?: "minute" | "hour" | "day" | "week" | "month" | false
snapStep?: number
style?: Record<string, string | number>
}
}
For nested or domain-specific records, pass item-accessor with getId, getStart, getEnd,
getConfig, setStart and setEnd. Setters are required only for interactive editing.
Interaction rules
overlap-layout="stack"gives overlapping bars stable independent sub-lanes.push-on-overlapmoves external obstacles;no-overlaprejects new collisions.bundlemoves related bars atomically while preserving offsets and durations.milestonerenders the item'sstartas a precise point;endis not required.- With
bar-label-overflow, bar and milestone content is measured at its rendered screen size and assigned to the lowest free sub-line; equal-time milestones stack in input order. dependsOnor chart-levelconnectionsmoves finish-to-start successors transitively.allow-row-dragrequires a stablerow-idandv-model:barson every source/target row.- Whole-bar moves preserve exact elapsed milliseconds; resizing intentionally changes duration.
- Native
Datevalues require:date-format="false".
Custom rendering
Scoped slots replace content without replacing positioning or interaction behavior:
| Component | Slot | Scope |
|---|---|---|
GGanttRow |
bar |
{ bar, config, lane, rowId } |
GGanttRow |
milestone |
{ bar, config, shape, lane, rowId } |
GGanttRow |
row-label |
{ label, rowId, bars, laneCount } |
GGanttChart |
tooltip |
{ bar, start, end } |
GGanttChart |
row-label |
{ label, rowId, bars, laneCount, index } |
GGanttChart |
upper-timeunit, timeunit |
{ label, value, date } |
Historical slot aliases remain available for migration.
State management
The default update-mode="mutable" preserves direct interactive updates. With
update-mode="controlled", the chart restores its temporary preview and emits typed change
patches for Pinia, Vuex, event sourcing or undo/redo. Interactive controlled objects must remain
writable during the preview; use the headless engine for frozen inputs.
Accessibility and performance
Bars expose keyboard controls, visible focus, ARIA descriptions and live announcements. Arrow keys move bars, Shift+Arrow resizes, Alt+Up/Down transfers rows, Enter commits and Escape restores. Visually hidden keyboard help and its live region are anchored inside the positioned chart, so they cannot extend the page outside a scrolling container. Reduced-motion preferences are respected.
Lane assignment is O(n log n) and collision queries use an interval index. Bars outside the chart
range are culled. Use optimize-rendering for browser containment or GGanttVirtualList<T> for
large independent resource lists.
Headless core
The core entry has no Vue or DOM dependency and never mutates its inputs:
import {
proposeMove,
createStepSnapPolicy,
rejectStationaryOverlaps
} from "@fdecourt/vue-ganttastic/core"
It accepts a generic TimelineAdapter<T> and optional snap, dependency and collision policies.
License
Vue Ganttastic is available under the MIT License.
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 3.0.8 | ^3.5.41 | MIT | |
| 3.0.7 | ^3.5.41 | MIT | |
| 3.0.6 | ^3.5.41 | MIT | |
| 3.0.5 | ^3.5.41 | MIT | |
| 3.0.4 | ^3.5.41 | MIT | |
| 3.0.3 | ^3.5.41 | MIT | |
| 3.0.2 | ^3.5.41 | MIT | |
| 3.0.1 | ^3.5.41 | MIT | |
| 3.0.0 | ^3.5.41 | MIT |