@lilydesignsystem/vue-gantt-chart
| Install | |
|---|---|
npm install @lilydesignsystem/vue-gantt-chart |
|
| Latest Version: | 0.8.0 |
| Vue: | 3 |
| License: | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause |
| Last Updated: | Oct 25, 2026 |
| Links: | GitHub · npm |
GanttChart (Vue helper)
A reusable Vue 3 headless interactive Gantt chart. It composes
@lilydesignsystem/vue-headless's GanttTable family — task bars
rendered as column-spanning grid cells, never pixel-positioned
floating divs — and @lilydesignsystem/vue-date-time-picker, used
twice per edit session (start date, end date), as the
keyboard-accessible way to reschedule or resize a task. Ships no CSS:
every visual detail is the consumer's, via kebab-case class hooks and
data-* attributes.
Editing is never drag-only. Native drag-and-drop fails WCAG 2.5.7
(Dragging Movements); the research behind this package (see
spec/index.md) found that even mature commercial
Gantt libraries ship no keyboard shortcut for dragging a bar — their
accessible path is a typed-field edit surface, which is exactly what
composing date-time-picker gives this package for free.
Usage
<script setup lang="ts">
import { ref } from "vue";
import GanttChart from "@lilydesignsystem/vue-gantt-chart";
import type { GanttTask } from "@lilydesignsystem/vue-gantt-chart";
const tasks = ref<GanttTask[]>([
{ id: "design", label: "Design", start: "2026-10-01", end: "2026-10-05" },
{ id: "build", label: "Build", start: "2026-10-06", end: "2026-10-12", dependsOn: ["design"] },
{ id: "launch", label: "Launch", start: "2026-10-13", end: "2026-10-13" }, // milestone
]);
function handleTaskChange(taskId: string, start: string, end: string) {
tasks.value = tasks.value.map((t) => (t.id === taskId ? { ...t, start, end } : t));
}
</script>
<template>
<GanttChart
label="Q4 launch plan"
:range="{ start: '2026-10-01', end: '2026-10-31' }"
:tasks="tasks"
today="2026-10-08"
@task-change="handleTaskChange"
:labels="{
columnLabel: (start) => start,
startLabel: 'Start date',
endLabel: 'End date',
saveLabel: 'Save',
cancelLabel: 'Cancel',
dependencySummary: (predecessors) => `Blocked by: ${predecessors.join(', ')}`,
dateAnnouncement: (title, start, end) => `${title} rescheduled to ${start} – ${end}`,
collapseButton: (task, collapsed) => (collapsed ? `Expand ${task.label}` : `Collapse ${task.label}`),
dateTimePickerLabels: {
previousYear: 'Previous year', previousMonth: 'Previous month',
previousWeek: 'Previous week', previousDay: 'Previous day',
nextDay: 'Next day', nextWeek: 'Next week',
nextMonth: 'Next month', nextYear: 'Next year',
confirm: 'Confirm', cancel: 'Cancel',
},
}"
/>
</template>
Props
See spec/index.md §5 for the
full table. Required: label, range, tasks. Every optional
labels.* field gates the control it names — editing itself is gated
on labels.dateTimePickerLabels being supplied, since
date-time-picker requires it too.
Events
task-change(taskId, start, end)— fires after a task's start/end changes, by pointer or by the edit region.
Behaviour
- Time axis:
range/timeUnit("day"default, or"week"/"month") generate a fixed set of columns using UTC/epoch-day arithmetic — never local-midnightDateconstruction. - Task bars: a
[start, end]range marks every overlapping column's celldata-in-range; a milestone (start === end) marks one celldata-milestone. - Row hierarchy:
task.parentIdbuilds a tree; a parent's ownstart/endare derived from its descendants and rendered read-only, with a collapse button that removes descendant rows from the DOM outright. - Dependencies:
task.dependsOnrenders as anaria-describedbytext summary; withdependencyArrowsanaria-hiddenSVG overlay also draws an arrow per dependency (0.6.0), so the text stays the accessible description. - Edit — keyboard: Enter/Space on a focused task row opens an
inline region with two composed
DateTimePickerinstances. - Edit — pointer: native HTML5 drag-and-drop reschedules a task; supplementary, never the only path.
- Keyboard: WAI-ARIA APG Grid roving-tabindex, the same model
data-gridandkanban-boarduse. - Announcements: every successful edit announces via one
aria-live="polite"region. - Typed fields and editing (0.5.0):
fields+task.valuesadd typed columns;field-editedits them in place (click, F2, Tab). See spec §11. - Export and virtualization (0.5.0):
labels.exportCsv/exportTsv/exportExcelpluslabels.exportHeadersadd CSV, TSV and Excel buttons;virtualizewindows long task lists.
Weekend shading and dependency arrows (0.6.0)
weekends(weekdays, 0 = Sunday … 6 = Saturday, never defaulted) marks day columnsdata-weekendon the header and every cell; the 57themes/tint them. Week and month columns are not marked.dependencyArrowsdraws anaria-hiddenSVG arrow (.gantt-chart-dependency-arrow,data-from,data-to,data-dependency-type) per dependency by its type (e2s,s2s,s2e,e2e), measured from the rendered cells; the text description (labels.dependencySummary) remains. See spec §17.
Filtering and sorting (0.7.0)
filter(task)hides tasks it rejects; a passing task keeps its ancestors, as a search match does, and it combines withsearch(both must pass).compare(a, b)orders tasks among their siblings — stable, and the hierarchy is kept (a child stays under its parent). Collapse, the visible export and keyboard order all follow it.
Export: copy to clipboard (0.7.0)
- Copy to clipboard:
labels.copynames a button (data-…-export-format="copy"; for the kanban board, Gantt chart and calendar it also needslabels.exportHeaders, like the downloads) that puts exactly what a download would hold — the same tasks, scope and order, header row first — on the clipboard as tab-separated text (toClipboardText: no byte-order mark, formula text defused), ready to paste into a spreadsheet.copyToClipboarduses the async Clipboard API and falls back to a hidden, selected<textarea>withexecCommand("copy"); it never throws.labels.copyAnnouncement(rowCount)is announced,labels.copyFailedwhen neither path worked, and thecopyevent andcopyHandler({ text, rowCount })may returnfalseto take over (nothing is copied).
Keyboard: page keys and type-ahead (0.7.0)
- PageUp / PageDown move the cursor ten task rows (a
virtualizepage when virtualized), clamping at the ends. - Type-ahead: letters typed in quick succession move to the next task whose label starts with them (the label is
taskLabel(task), ignoring case and diacritics; the same letter again steps through matches). A lonesstays the select shortcut; it joins a prefix already in progress. A Ctrl, Alt or Meta key, and an unmatched prefix, do nothing.
In-place edit: validation, undo and redo (0.7.0)
- Field
validate(value, values)refuses a value of the right type that is not acceptable (a negative estimate, an end before the start): return the reason. The editor stays open witharia-invalid="true"(cleared as soon as the user types again) andlabels.editRefused(task, header, reason)is announced, falling back tolabels.editInvalid. - Undo and redo: every accepted edit is remembered (the latest 100). Ctrl/Cmd+Z puts the earlier value back and Ctrl/Cmd+Shift+Z (or Ctrl+Y) re-applies one, both through the
field-editevent with the restored value and the one it replaced — the consumer owns the data, so an undo is reported like an edit, and an undo is not itself recorded. A new edit ends the redo chain. Inside a field editor the browser's own undo is left alone; nothing happens in areadonlycomponent or without thefield-editevent.labels.undoAnnouncement(task, header, text)/labels.redoAnnouncement(task, header, text)are announced. The stacks are the pureedit-history.ts(recordEdit,undoEdit,redoEdit,historyKey).
Weekend, holiday and time-off indicators (0.7.0)
unavailableis a list of dated ranges{ start, end?, kind?, label? }for holidays, time off and closures (enddefaults tostart; Lily ships no calendar, no kinds and no English). A column every day of which is covered getsdata-unavailable="<kinds>"(the entries'kinds,unavailablewhen none, repeats removed) on its header and every cell; a column only partly covered getsdata-partly-unavailableinstead, so a holiday inside a week column is still marked. Alabelis rendered as text in the header (.gantt-chart-unavailable-label), so a day is never told apart by colour alone (WCAG 1.4.1). The weekend isweekends;markersflag one date;isNonWorkingis a callback. Matching is the pureunavailable.ts(unavailableOn,unavailableIn,unavailableKinds), time-zone free.- Shading is CSS and ships in the 57 reference themes: weekends are tinted, unavailable days are tinted and striped, a partly unavailable column gets a bar under its header.
Direction and labels (0.7.0)
- Right-to-left. The horizontal arrows are visual: in a right-to-left layout the later date is on the left, so ArrowLeft goes to the next day, and Alt+ArrowLeft/Right shifts a task (and Alt+Shift+Arrow moves only its end) the way the arrow points. Vertical keys, Home, End, page keys and type-ahead are unchanged.
isRtl/logicalKeyare exported. - No English left over. A complete Chinese bundle (labels, tasks, markers, unavailable days) is tested to leave no English text or
aria-labelin the DOM; every string is inlabels. - Tests §18.43–§18.48.
Search (0.7.0)
search (a v-model; the field renders when labels.search names it) narrows the tasks: every word must appear, in any order, ignoring case and diacritics. New: the Gantt had no search. Matching tasks show with their ancestors (so the hierarchy stays readable); searching ignores collapse so a match under a collapsed parent is found, and clearing the search restores it. The default text is the task label and every field's text (searchText replaces it). Matches in the label are wrapped in <mark class="gantt-chart-match">; labels.searchAnnouncement(matches, total) is announced; Escape clears. exportScope: "visible" exports only what the search leaves. See spec §18.
Critical path (optional helper)
The chart renders critical and slack; it computes nothing. applyCriticalPath(tasks) (or criticalPath(tasks) for the raw result) fills them in: a forward and backward pass over leaf tasks, all four dependency kinds (e2s, s2s, e2e, s2e) with lags and leads in calendar days. A task's own start is a floor, so dependencies only push tasks later; summary rows, unscheduled tasks and unknown ids are ignored; a dependency cycle is reported in cycle and left out. Show it with labels.criticalLabel.
Lag is in calendar days by default. For schedules that count business days, pass { lagUnit: "working-days", weekends, unavailable } (the same weekends and unavailable you give the chart for shading): a lag of N then moves N working days, skipping weekends (default Saturday and Sunday) and the listed holidays, and the backward pass keeps a predecessor off non-working days so a weekend is never reported as slack. Task spans stay as dated; only the gaps between tasks change.
import { applyCriticalPath } from "@lilydesignsystem/vue-gantt-chart";
const tasks = applyCriticalPath(rawTasks); // each leaf task gains critical / slack
Non-goals (v1)
Critical-path calculation, interactive zoom-level
switching, holiday shading from a calendar (isNonWorking marks them; weekends marks weekdays), resource/assignee columns. See
spec/index.md §9 for why each is out.
Lily™ and Lily Design System™ are trademarks.
Related Packages
Lily Design System Vue 3 headless kanban board (pointer + keyboard-accessible ca...
Lily Design System Vue 3 headless interactive data grid (sort, filter, selection...
A comprehensive headless Vue 3 component library with 491 accessible, unstyled c...
Lily Design System Vue 3 locale picker: an icon button opening an APG listbox th...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.8.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.7.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.6.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.5.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.4.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.3.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.2.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause | |
| 0.1.0 | ^3.0.0 | MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause |