Skip to content

Gantt ​

Project-scheduling chart component (data family): a WBS row tree (parent summary bars span the union of their children's time) against a horizontal time axis, with three bar forms (task / milestone / summary) plus progress fill; dependency links (FS/SS/FF/SF, drawn on a single SVG overlay); a drag trio (drag to reschedule, edge handles to resize, in-bar handle to change progress — written back once on pointerup with events); six time scales (hour/day/week/month/quarter/year) with a two-tier header; today line and weekend/holiday column highlights; row virtualization for tens of thousands of tasks; three readonly levels plus per-task disabling. The data contract is a flat parent pointer list (nested children input is flattened automatically).

Basic Usage ​

WBS row tree + dependency links + milestones + progress (tasks JSON; flat parent / nested children both accepted)

  • Parent tasks render summary bars (time = union of children, progress = duration-weighted average); the row toggle collapses/expands children; the expanded set is written back to the expanded attribute (JSON array) with oas-expand-change; collapsed sub-rows hide together with their dependency links (links follow row visibility, they do not reroute to the summary bar);
  • Milestones render as diamonds: an explicit type: "milestone" takes precedence (a task renders as a milestone even when it carries end; the end is ignored with a one-time console.warn about the data conflict); tasks without end also infer as milestones; a task with children always renders as a summary bar (children carry the aggregation semantics, so form and aggregation must agree; a one-time console.warn about the data conflict fires only when type: "milestone" is also declared explicitly); a start after end (reversed dates) is swapped automatically at the sanitize layer with a one-time console.warn (no 2px ghost bar);
  • dependencies declare links (type defaults to fs; fs/ss/ff/sf supported) drawn as polylines with arrowheads on a single SVG canvas;
  • Hovering a bar shows a tooltip (name + range + progress); template[slot="tooltip"] overrides the content (bind points [data-task-name] / [data-task-range] / [data-task-progress]).

Time Scales (scale) ​

Six scales: hour / day (default) / week / month / quarter / year (two-tier header: merged major tier + per-unit minor tier)

Changing scale re-lays the time axis (column width adapts per tier, with a floor that keeps labels legible) and dispatches oas-scale-change; invalid values fall back to day. When the container squeezes columns too narrow for labels, minor ticks are thinned by N (grid lines kept, some texts hidden — e.g. every 2nd day on a narrow day axis) so day/month numbers never collide. Dragging snaps to the current scale unit (snap="false" disables snapping and shifts by pixel ratio, preserving time precision).

Drag Trio ​

Drag body to reschedule / edge handles to resize / dot handle for progress (written back once on pointerup; Esc or pointercancel cancels with zero events)

  • Drag the body = move as a whole (duration kept); drag edge handles = change start/end (cannot cross the opposite side); drag the in-bar dot = change progress;
  • Written back on pointerup with oas-task-change (detail: { id, task, oldStart, oldEnd, start, end }) / oas-progress-change (detail: { id, task, progress }) plus oas-tasks-change (full list); releasing without movement dispatches nothing;
  • Esc (or pointercancel) while dragging cancels → visual rollback, zero events; rewriting tasks externally mid-drag aborts the drag (external data wins);
  • With a bar focused, Shift + ←/→ reschedules by one unit (mirrored in RTL);
  • While dragging a bar / resizing / drawing a link, moving near a viewport edge auto-scrolls horizontally and vertically (faster the closer to the edge; stops at the boundary); row reorder auto-scrolls vertically only.

Row Reparent / Reorder (reparent) ​

Drag a left-column row: top/bottom half = before/after the target (same level), pointer deep to the right = become a child of the target; settles with oas-reparent

Drag a left-column row onto a target: the top half inserts before the target (same level); the bottom half inserts after it (same level); a pointer deep to the right makes it a child of the target. On settle it dispatches oas-reparent (detail: { id, parent, index }, parent null means root level) plus oas-tasks-change (full list). Dropping a parent into its own descendant is rejected (cycle guard, zero events); readonly or a per-task disabled never starts the drag; Esc / pointercancel cancels with zero events.

Hover a bar for link dots: drag from the left/right dot onto a target task to create a dependency (endpoints pick FS/SS/FF/SF); click a link to select, then Delete to remove

Hovering a bar reveals two link dots: drag the right (finish) dot onto a target bar to create a dependency; the left (start) dot works the same way. A milestone has start == end, so it shows a single finish dot (fs convention; its ss/sf as a predecessor are equivalent to fs/ff). The drop side of the target — left half (start) / right half (finish) — combined with the source endpoint picks the type (FS / SS / FF / SF). The dependency is recorded on the successor task. Click an existing link to select (highlighted), then press Delete / Backspace to remove it (only while the component holds focus; host inputs are not hijacked; pressing Delete on a focused task bar does not delete a selected link). On settle it dispatches oas-dependencies-change (detail: { taskId, dependencies }) plus oas-tasks-change. With readonly, link dots and hit areas are not rendered and removal is disabled; Esc / pointercancel cancels with zero events. On touch, tap a bar to reveal its link dots; a link hit path is focusable and Enter/Delete removes that dependency.

Multi-select, Rename & Keyboard ​

Click a row to select (Ctrl/Cmd adds, Shift for a range); double-click the name to rename; Delete removes the selection; Alt+↑/↓ reorder siblings, Alt+←/→ outdent/indent; focus a link and press Enter to delete

  • Multi-select: click a left row name to select (single); Ctrl/Cmd click toggles; Shift click selects a range; state is exposed via the selection property (id array, readable/writable) and the oas-selection-change event. Delete/Backspace removes the selection (with descendants), or call removeTasks(ids).
  • Inline rename: double-click a name → input (Enter commits / Esc restores / blur commits), updates name and dispatches oas-tasks-change.
  • Keyboard: with a row focused, ↑/↓ move focus, Space selects, Alt+↑/↓ reorder among siblings, Alt+→ indents under the previous sibling, Alt+← outdents, F2 renames; a link hit path is focusable, Enter/Delete removes that dependency (focus returns to the component after removal, so the keyboard position is not lost).
  • Accessibility: the name column is a role="tree"; each treeitem carries aria-level / aria-posinset / aria-setsize (full sibling set, including collapsed children; dangling/cyclic parents count as roots); selection mirrors to aria-selected and expansion to aria-expanded.
  • Touch: tap a bar to reveal its link dots (no hover); the drag handle (⋮⋮) at the start of a left row drives touch row reorder (a full-row vertical drag conflicts with list scrolling on touch, so the handle carries it; the handle owns the gesture while the rest of the row still scrolls). A plain tap on the handle does not change selection.

Task Management (Create / Edit / Dependencies / Export) ​

Drive create/update/delete with imperative methods instead of rewriting tasks wholesale (writes go through the sanitizer and dispatch oas-tasks-change):

  • addTask(task?, opts?): add a task, returns the new id; opts: { parent?, before?, after? }. Defaults: name from i18n gantt.newTask, start = today, non-milestone gets a 1-day (inclusive) end.
  • moveTask(id, { parent?, index? }): reparent / reorder; dispatches oas-reparent + oas-tasks-change.
  • duplicateTask(id, opts?): deep-copy the task and its subtree (fresh ids), right after the original subtree; returns the new root id.
  • addDependency(taskId, dep) / removeDependency(taskId, dep) / updateDependency(taskId, from, to): add / remove / rewrite a dependency (dep: { id, type? }); dispatches oas-dependencies-change.
  • updateTasks(patches): batch shallow-merge ([{ id, ...patch }]), returns the applied count, dispatches a single oas-tasks-change.
  • Zoom: zoomIn() / zoomOut() step through the six scales; fit() picks the finest scale that still fits all tasks.
  • Attribute creatable (boolean, off by default): enables the trailing "+" per row (adds a subtask on summary rows, a sibling after on leaf rows) and double-click empty space to create a task at the clicked date; the new task enters inline rename. No button on readonly / per-task-disabled rows.
  • Event oas-task-add (detail { id, task }): dispatched after imperative or inline creation, so the host can locate and open an editor.

Imperative task management: add / duplicate selected / add dependency / delete selected / zoom / export; creatable inline add (click the trailing + or double-click empty space)

Add taskDuplicate selectedAdd dependency (selected → next)Delete selectedProgress +10Zoom outZoom inFitExport JSON

Assemble a task detail editor with oas-modal + oas-form (the component only dispatches oas-task-dblclick; no built-in dialog):

html
<oas-gantt id="g" creatable></oas-gantt>
<oas-modal id="edit" title="Edit task">
  <oas-form id="edit-form">
    <oas-form-item label="Name" name="name" required><oas-input name="name"></oas-input></oas-form-item>
    <oas-form-item label="Start" name="start" required><oas-date-picker name="start"></oas-date-picker></oas-form-item>
    <oas-form-item label="End" name="end"><oas-date-picker name="end"></oas-date-picker></oas-form-item>
    <oas-form-item label="Progress" name="progress"><oas-slider name="progress" show-value></oas-slider></oas-form-item>
  </oas-form>
</oas-modal>
<script>
  const g = document.getElementById('g')
  const modal = document.getElementById('edit')
  const form = document.getElementById('edit-form')
  const field = (name) => form.querySelector(`oas-input[name="${name}"], oas-date-picker[name="${name}"], oas-slider[name="${name}"]`)
  let editing = ''
  g.addEventListener('oas-task-dblclick', (e) => {
    editing = e.detail.id
    const t = e.detail.task
    field('name').value = t.name
    field('start').value = t.start
    field('end').value = t.end ?? ''
    field('progress').value = t.progress ?? 0
    modal.setAttribute('visible', '') // controlled open (the modal closes itself on OK)
  })
  modal.addEventListener('oas-ok', () => form.submit())
  form.addEventListener('oas-submit', (e) => {
    const v = e.detail.values // form values are always strings; coerce numeric fields as needed
    g.updateTask(editing, { ...v, progress: Number(v.progress) })
  })
</script>

oas-task-dblclick / oas-task-click and oas-task-render let the host own editing and custom rendering; the component only guarantees a closed loop of data and interaction.

Task Management Extras (Auto-scheduling / Marquee / Drag-to-create) ​

  • Dependency auto-scheduling: auto-schedule (boolean, off by default) — after a date change (drag / keyboard / updateTask / updateTasks) or a dependency add/update, successors are pushed minimally later (duration preserved) along FS/SS/FF/SF; skips disabled and summary rows; cycle-safe; dispatches oas-reschedule (detail { ids }) plus the resulting oas-tasks-change.
  • Marquee multi-select: drag vertically on empty left-list space to select by row band; drag on empty timeline space (when creatable is off) to select by bar intersection; Ctrl/Cmd adds. The result is written to selection and dispatches oas-selection-change; marquee still works in readonly (selection is a view state).
  • Drag-to-create: with creatable on, drag a range on empty timeline space to create a task spanning that range (double-click still creates a single-day task).

Touch: a timeline-space drag conflicts with 2D scrolling, so marquee / drag-to-create on the timeline do not claim touch gestures (native scrolling is left alone); on touch, create via the trailing "+" or double-click, and marquee via a vertical drag on the left list. Under auto-schedule, when a summary task is a predecessor, its aggregated descendant range is used (the summary's own date fields are ignored).

auto-schedule: drag the “Prototype” bar right and “Implementation” follows via FS; drag empty timeline space to create a task; drag empty left-list space to marquee-select rows

Scheduling Depth (Dependency lag/lead + Critical Path) ​

  • Dependency lag/lead: dependencies: [{ id, type?, lag? }] — lag is in days and may be negative (negative = lead/overlap), folded into the auto-schedule constraints (fs: successor start ≥ predecessor end + lag; ss: successor start ≥ predecessor start + lag; ff/sf likewise on end). Defaults to 0 (backward compatible). Values outside ±11574 days (≈31 years) or non-finite values are treated as 0 with a one-time devWarn (extreme values would otherwise produce 5-digit years the component cannot read back).
  • Critical path: show-critical-path (boolean, off by default) — with the current schedule as the baseline (ES taken from the current start), runs a backward pass over the dependency graph + durations to compute total float; tasks with zero float get a .critical highlight on their bar (color via --oas-gantt-critical-color). Read-only; does not change data.
  • Float marks: total float 0 → .critical (solid outline); negative total float (already violating a successor constraint) → .conflict (dashed outline, --oas-gantt-conflict-color). A fractional lag (e.g. 0.5) in el.tasks promotes that row's date strings to time precision (round as needed).

Critical path: a → b (long) and a → c (short) both follow a — b is highlighted on the critical path, c has float and is not (turn show-critical-path off to drop the highlight)

Scheduling Constraints & Working-Days Calendar ​

  • Task constraints: GanttTask.constraint ('asap' default / 'snet' start-no-earlier-than / 'mso' must-start-on / 'fnlt' finish-no-later-than) + constraintDate. Under auto-schedule: snet sets a start floor, mso pins the start (not pushed by dependencies), fnlt is a soft deadline (no auto-move). Violations (snet earlier than the floor / mso conflicting with dependencies or off the pin / fnlt over deadline) are marked .conflict when show-critical-path or auto-schedule is on. Constraints on summary rows are meaningless (dates are derived) → ignored with a one-time warning.
  • Working-days calendar: workdays (JSON weekday array [0..6], opt-in; absent/empty = any weekday) + holidays (holidays are always non-working, regardless of workdays). auto-schedule snaps pushed landing points to the next working day (skipping holidays and days outside workdays). Changing workdays/holidays at runtime, or rewriting tasks, re-normalizes.
  • Event order: with auto-schedule on and dependent corrections applied, the order is uniformly oas-reschedule (dependent corrections) → oas-tasks-change (final corrected data), whether the change comes from an interaction commit (drag / keyboard / updateTask / updateTasks) or the host rewriting tasks directly (attribute channel); a drag/keyboard settle then dispatches oas-task-change (the dragged task, with start/end carrying the corrected final values). One operation dispatches oas-tasks-change exactly once. Hosts should read el.tasks / detail.task.

Working-days calendar: with workdays=[Mon–Fri], drag the predecessor and the successor snaps to the next working day; a +2 (lag) label is visible on the link

Host Integration Recipes (Undo/Redo / Edit Dialog / Export) ​

The component only guarantees a closed loop of data + interaction; the capabilities below are provided as official recipes (no changes to the component itself):

Undo / Redo (host-owned history) ​

This is a controlled-data model: every change dispatches oas-task-change (with oldStart/oldEnd/start/end) plus oas-tasks-change. Keeping your own history stack from these events is the most robust approach — a built-in stack would create a second source of truth against the host tasks: undo writes internally then gets overwritten by the host, granularity mismatches host actions, and controlled re-renders bounce the undo back. Typical approach: listen to oas-task-change / oas-reparent / oas-dependencies-change, record inverse operations, and write back via updateTask / moveTask / updateTasks.

Edit dialog (double-click a task bar) ​

The component only dispatches oas-task-dblclick; compose a detail editor with oas-modal + oas-form:

Double-click a task bar to open the edit dialog (oas-modal + oas-form); Save writes back via updateTask

Export (JSON / print / image) ​

  • JSON: el.tasks is serializable data (nested input is flattened) — JSON.stringify(el.tasks).
  • Print: add @media print to expand the timeline and hide unrelated UI; or call fit() then window.print().
  • Image / PDF: render on the host side (HTML→canvas or SVG serialization), e.g. new XMLSerializer().serializeToString(el.shadowRoot.querySelector('.inner')) then to canvas.

Export: click to output the current tasks JSON (the host can persist or download it)

Export JSON

Readonly Levels + Per-task Disable ​

readonly (all) / dates-readonly (no rescheduling) / progress-readonly (no progress drag); readonly states render no drag handles (visual = semantics)

readonly: whole chart read-only
dates-readonly: dates locked, progress still draggable
progress-readonly: progress locked, dates still draggable

disabled: true in task data locks only that task's own data: it cannot be dragged (as a source), cannot be a dependency successor, and cannot be a reparent drop-inside target; it can still act as another task's predecessor (link source) and as a reparent before/after sibling target.

Today Line & Weekend/Holiday Highlights ​

Today line (show-today on by default) + weekend column highlights (weekends on by default) + holidays (demo range dynamically covers today)

Today line off / weekend highlights off / holiday highlights (holidays JSON)

The today line is decorative (aria-hidden) and renders only when today falls inside the axis range; weekend/holiday column tints apply on the day/hour tiers.

Baseline Comparison (show-baseline) ​

show-baseline on: a translucent baseline bar under the actual bar, title showing the delta (N days later/earlier, or on baseline)

With show-baseline on (off by default), a task carrying baseline: { start, end? } renders a translucent baseline bar under the actual bar; the bar title shows the actual-vs-baseline delta (N days later/earlier, or "on baseline"). Baseline data is owned by the host; the component is read-only on it. baseline.progress is baseline progress 0-100; when present, a progress overlay renders inside the baseline bar (--oas-gantt-baseline-progress-color).

Deadlines, Float & Baseline Variance (deadline / show-float / baseline.progress) ​

show-baseline + show-float: baseline progress, total/free float; a missed deadline gets .conflict

  • GanttTask.deadline: due date (YYYY-MM-DD; hour scale may include time). When the bar ends after it, the bar gets .conflict and the chart renders a dashed .deadline marker.
  • show-float: shows total float (LF − EF) and free float (slack before the nearest successor); the bar also exposes data-total-float / data-free-float in days. Summaries are excluded from CPM; windowed mode skips it with a devWarn because a slice lacks the full dependency graph.
  • baseline.progress: baseline progress 0-100; with show-baseline, a .baseline-progress overlay renders inside the baseline bar.
  • Hosts can use the exported pure function computeVariance(tasks) for baseline variance:
ts
import { computeVariance } from '@oas-ui/ui'

const rows = computeVariance(el.tasks)
// [{ id, startDelta, endDelta, durationDelta, progressDelta }]
// Positive = later than baseline / longer duration / more progress; progressDelta is null without baseline.progress

Row Virtualization (10k-scale tasks) ​

height + row-height enable viewport windowing (300 rows demoed; only the visible window + buffer renders)

Methods: scrollToTask row 250

Methods: scrollToTask(id) to bring a task row into view, scrollToDate(date) / scrollToToday() for horizontal positioning, updateTask(id, patch) for single-task incremental updates (sanitized, dispatches oas-tasks-change).

Server-side row window (windowed) ​

For hundreds of thousands of rows: the host supplies only the slice near the visible window and the component holds no full dataset. The row window is computed from the scroll position and, when not covered by the loaded range, oas-window-request is emitted; the host refills and rows render by absolute row index.

Attributes: windowed (enable) + total (full row count) + window-start (absolute row index of the slice's first row) + range-start / range-end (host-managed time-axis extent, ISO dates, range-end inclusive — required when the slice does not span the full date range). Events:

  • oas-window-request: detail: { start, end, reason } (half-open, absolute row indices; reason = init / scroll / resize / data); the same expected window is not emitted twice.
  • oas-range-request: emitted when the visible time window approaches an axis edge; detail: { startMs, endMs, reason } for the host to extend the range.
html
<oas-gantt windowed total="200000" window-start="0" range-start="2026-01-01" range-end="2026-12-31"
  height="360" scale="day"></oas-gantt>
<script>
  const el = document.querySelector('oas-gantt[windowed]')
  el.addEventListener('oas-window-request', (e) => {
    const { start, end } = e.detail
    el.setAttribute('window-start', String(start))
    el.setAttribute('tasks', JSON.stringify(fetchTasks(start, end))) // fetch from your backend
  })
</script>

Conventions in windowed mode: the slice must be flat (a hierarchical slice with parent/children triggers a devWarn and renders flat — flatten on the host side); auto-schedule and the critical path would be wrong on partial data and are skipped with a devWarn; row drag-reorder is disabled (index only makes sense within the slice); out-of-range slices (window-start + loaded rows > total) are clamped to total (overflow rows are not rendered) with a one-time devWarn; cross-window links are not drawn — if the predecessor is outside the loaded slice, that dependency link is skipped (the component only holds the slice and cannot locate an off-slice endpoint; to show complete links, include the relevant predecessors in the slice or render them in an aggregation layer); one user action emits oas-tasks-change exactly once; oas-tasks-change carries only the slice in detail.tasks — the host must merge by id into the full dataset, never replace it wholesale.

For the full host recipe (segment fetch, abort in-flight, merge by id, atomic refill) and a runnable example, see Server-side windowed data.

Controlled Expansion / Labels & Snapping / Localization ​

  • Custom fields pass through: GanttTask may carry arbitrary business fields (e.g. assignee, tag) — they are preserved as-is (visible in el.tasks / oas-task-render / templates) and do not affect scheduling; render them via the bar-label template or oas-task-render, and edit via the oas-modal+oas-form recipe or updateTask.
  • Custom row-name template: <template slot="row"> is cloned per row into .name, where [data-task-name] gets the task name; it may hold any controls — this is where an assignee column goes (carry the assignee as a custom field; the template renders and edits it). Controls inside the template are not hijacked by row drag/select. Example:
html
<oas-gantt id="g">
  <template slot="row">
    <span data-task-name></span>
    <oas-select class="owner" size="small" options='[{"label":"Alice","value":"alice"},{"label":"Bob","value":"bob"}]'></oas-select>
  </template>
</oas-gantt>
<script>
  const g = document.getElementById('g')
  const tasks = [{ id: 'a', name: 'Design', start: '2026-03-02', end: '2026-03-06', owner: 'alice' }]
  g.tasks = tasks
  // Fill per-row controls after cloning
  g.shadowRoot.querySelectorAll('.row-name').forEach((row) => {
    const t = tasks.find((x) => x.id === row.dataset.key)
    row.querySelector('.owner').value = t.owner
  })
  // Change owner → write back the custom field
  g.addEventListener('oas-change', (e) => {
    const row = e.target.closest('.row-name')
    if (row && e.target.classList.contains('owner')) g.updateTask(row.dataset.key, { owner: e.target.value })
  })
</script>

expanded as a controlled set (JSON array = expanded set; explicit [] collapses all) + label-position (default none, since the row-name column already shows names; inside / right must be opted in)

snap=false (pixel-ratio shift keeps time precision) + locale / first-day-of-week

template[slot="task"] overrides in-bar label content ([data-task-name] binding; labels are not rendered by default with label-position="none", so the template takes effect only after opting in); each rendered bar dispatches oas-task-render (detail: { task, element }) so hosts can rewrite its DOM.

API ​

oas-gantt ​

Attributes ​

AttributeDescriptionTypeDefault
auto-scheduleDependency auto-scheduling (boolean, off by default): after a date change (drag/keyboard/updateTask/updateTasks) or a dependency add/update, successors are pushed minimally (later only, duration preserved) along FS/SS/FF/SF; skips disabled and summary rows; cycle-safe. Dispatches oas-reschedulestring—
creatableInline creation (boolean, off by default): when enabled, a trailing "+" appears per row (adds a subtask on summary rows, a sibling after on leaf rows) and double-clicking empty space creates a task at the clicked date; the new task enters inline rename. No button on readonly or per-task-disabled rowsboolean—
dates-readonlyNo rescheduling/resizing (progress still draggable)boolean—
expandedExpanded set (JSON array, controlled): defaults to fully expanded when absent; explicit [] collapses all; written back on togglestring—
first-day-of-weekWeek start day (0-6); defaults to locale derivation (used for week-tier alignment)string—
heightComponent height in px (default 320; accepts a plain number or Npx): mapped to the custom property --oas-gantt-height. The height is applied to the internal canvas (.gantt) — the host's own inline height only frames the outer box, while the inner canvas height is still governed by the height attribute / --oas-gantt-height and is not stretched by the host's inline height; to change the overall height use the height attribute or --oas-gantt-height. While the height attribute is present and valid (>0 plain number or Npx) the component takes over this variable — writing it inline on the host and overriding any --oas-gantt-height the host set itself; an empty/whitespace value is treated as unset; an invalid value writes nothing and only clears a value this instance wrote earlier, so a host token is never affected; after the attribute is removed the component-written value is cleared to zero, so the height falls back to the token default (320) or host CSS, and the host's previous inline value is not restored; when the height attribute was never set the component never writes the variable, and a --oas-gantt-height set by the host (inline or via class rules) is left completely untouchedstring320
holidaysHoliday list (JSON ["YYYY-MM-DD",…]): matching columns highlighted (takes precedence over weekend tint); holidays are always non-working (regardless of workdays) and are skipped by auto-schedulestring—
label-positionBar label position: inside / right / left; default none (the row-name column already shows the name, so the bar does not repeat it); invalid values fall back to nonestring—
localeLocalization (overrides config-provider injection and the global locale)string—
progress-readonlyNo progress dragging (dates still draggable)boolean—
range-endHost-managed time-axis end (ISO date YYYY-MM-DD, inclusive); pair with range-startstring—
range-startHost-managed time-axis start (ISO date YYYY-MM-DD): when provided the axis extent is no longer derived from data; use with range-end to support windowed mode (the slice may not span the full date range)string—
readonlyWhole chart read-only (no rescheduling/progress, drag handles not rendered)boolean—
row-heightRow height in px (default 36, minimum 20)string36
scaleTime scale: hour / day (default) / week / month / quarter / year; invalid values fall back to daystring—
show-baselineBaseline comparison (boolean, off by default): tasks with baseline render a baseline bar under the actual bar (baseline.progress renders an overlay), with a title showing the delta (N days later/earlier or same)boolean—
show-critical-pathCritical path (boolean, off by default): runs a CPM pass over the dependency graph + durations to compute total float and adds a .critical highlight to zero-float tasks (color via --oas-gantt-critical-color); read-onlystring—
show-floatShow total/free float (boolean, off by default): computed only on a complete dependency graph; the bar gets a total/free float label plus data-total-float/data-free-float in days. Summaries are excluded; windowed mode skips it with a devWarn.string—
show-todayToday line (on by default; false to disable)string—
snapSnap drags to the current scale unit (on by default; false shifts by pixel ratio and keeps time precision)string—
stripeRow zebra striping (boolean, off by default): tints rows by the absolute row index parity (stable under virtualization/windowing, no DOM-order flicker); color via --oas-gantt-row-stripe-bg (default --oas-color-bg-hover); selection/weekend backgrounds override the stripestring—
tasksGantt tasks (JSON): [{ id?, name, start, end?, type?, progress?, parent?, children?, dependencies?, color?, disabled?, expanded?, baseline? (start/end?/progress?), deadline?, constraint? ('asap'|'snet'|'mso'|'fnlt'), constraintDate? }] — flat parent pointers are the primary contract; nested children are flattened automatically; explicit type:"milestone" takes precedence (rendered as milestone even with end, which is ignored) but a task with children is always a summary; no end also infers milestone; start/end are swapped automatically when start is after endGanttTask[][]
totalFull row (task) count in windowed mode; 0 or invalid disables windowed (falls back to local mode)string—
weekendsWeekend column highlights (on by default; applies on day/hour tiers; false to disable)string—
window-startAbsolute row index (within the full dataset) of the first row in the slice (windowed mode)string0
windowedServer-side row-window mode (boolean, off by default): with total / window-start the host supplies only the visible window slice (rendered by absolute row index); when the expected row window is not covered, oas-window-request is emitted for the host to refill. Local full-dataset capabilities (auto-schedule / critical path) would be wrong on partial data and are skipped with a devWarn. Non-windowed behavior unchangedboolean—
workdaysWorking weekdays (JSON [0-6], 0=Sunday…6=Saturday; opt-in, absent/empty = unrestricted): auto-schedule results snap to the next working day (combined with holidays)string—

Property (JS property; see each row for attribute reflection) ​

PropertyDescriptionTypeDefault
selectionSelected task ids (string[]): el.selection reads (copy); el.selection = [...] writes (re-renders selection + dispatches oas-selection-change); removeTasks(ids) batch-removes (with descendants)string[]—
tasksGantt tasks (GanttTask[]): el.tasks reads (copy; nested input already flattened to parent pointers); el.tasks = [...] writes (syncs the tasks attribute + re-renders + dispatches oas-tasks-change)GanttTask[][]

Events ​

EventDescription
oas-dependencies-changeDispatched when a link drag creates a dependency or a selected link is deleted, detail: { taskId, dependencies } (taskId is the successor whose dependencies changed)
oas-expand-changeDispatched on row tree collapse/expand, detail:
oas-progress-changeDispatched when a progress handle drag settles, detail:
oas-range-requestEmitted when the time axis is host-managed (range-start/range-end) and the visible time window approaches an axis edge; detail: { startMs, endMs, reason } (the neighbouring range requested). The same expected range is not emitted twice
oas-reparentDispatched when a row drag reparent/reorder settles, detail: { id, parent, index } (parent null means root level; index is the sibling position)
oas-rescheduleDispatched when auto-scheduling moved successor tasks (auto-schedule on), detail: { ids } (ids of the rescheduled tasks)
oas-scale-changeDispatched when the scale attribute changes, detail: { scale }; not dispatched on initial absorption
oas-selection-changeDispatched when the multi-select set changes (click / keyboard / property write), detail: { ids } (selected task ids)
oas-task-addDispatched after imperative creation (addTask / duplicateTask / creatable inline create), detail: { id, task } (id is the new task id)
oas-task-changeDispatched when a drag/resize/keyboard reschedule settles (pointerup), detail: { id, task, oldStart, oldEnd, start, end }; not dispatched on zero movement
oas-task-clickDispatched on task bar/milestone click, detail:
oas-task-dblclickDispatched on task bar double-click, detail: { id, task } — hosts attach an edit form
oas-task-renderDispatched after each task bar renders, detail: { task, element } — hosts may rewrite bar DOM
oas-tasks-changeDispatched on task data change (programmatic tasks write / updateTask / drag write-back), detail:
oas-window-requestEmitted in windowed mode when the expected row window is not covered by the loaded range; detail: { start, end, reason } (half-open, absolute row indices; reason is init / scroll / resize / data). The same expected window is not emitted twice

Slots ​

NameDescription
template[slot="empty"]Empty-state content (defaults to "No tasks")
template[slot="row"]Custom row-name template (cloned per row into .name; [data-task-name] gets the task name; may contain controls to render/edit custom fields such as an assignee; row drag/select does not hijack those controls)
template[slot="task"]Task bar label template (cloned into the bar; [data-task-name] nodes get the task name)
template[slot="tooltip"]Hover tooltip template ([data-task-name] / [data-task-range] / [data-task-progress] bind points)

CSS Variables ​

CSS VariableDescriptionDefault
--oas-gantt-bar-bg—color-mix(in srgb, var(--oas-gantt-bar-color, var(--oas-color-primary)) 22%, var(--oas-color-bg))
--oas-gantt-bar-height—22px
--oas-gantt-bar-progress-bg—var(--oas-gantt-bar-color, var(--oas-color-primary))
--oas-gantt-baseline-colorBaseline bar color (default translucent secondary text)color-mix(in srgb, var(--oas-color-text-secondary-strong) 55%, transparent)
--oas-gantt-baseline-progress-color—var(--oas-color-text-secondary-strong)
--oas-gantt-conflict-colorConflict (negative-float) highlight color (default --oas-color-danger)var(--oas-color-danger)
--oas-gantt-critical-colorCritical-path highlight color (default --oas-color-danger)var(--oas-color-danger)
--oas-gantt-deadline-colorDeadline marker color (default --oas-color-danger)var(--oas-color-danger)
--oas-gantt-grid-line-color—var(--oas-color-border)
--oas-gantt-header-bg—var(--oas-color-bg)
--oas-gantt-header-height—44px
--oas-gantt-height—320px
--oas-gantt-holiday-bg—color-mix(in srgb, var(--oas-color-warning) 12%, transparent)
--oas-gantt-link-color—var(--oas-color-primary)
--oas-gantt-list-width—220px
--oas-gantt-milestone-color—var(--oas-color-warning)
--oas-gantt-row-height—36px
--oas-gantt-row-stripe-bg—var(--oas-color-bg-hover)
--oas-gantt-summary-bg—var(--oas-color-text-secondary-strong)
--oas-gantt-today-color—var(--oas-color-danger)
--oas-gantt-weekend-bg—color-mix(in srgb, var(--oas-color-bg-hover) 60%, transparent)

Released under the MIT OR Apache-2.0 License.