Gantt
A project timeline with draggable task bars, dependencies, milestones, hierarchy, and a real commit gate. Renders with a Razor-first engine — no third-party charting library.
Installation
dotnet add package Lumeo.Gantt
One-time app setup (AddLumeo(), CSS & JS) is covered in the
installation guide.
Usage
@using Lumeo <GanttChart />
679 tests across 51 files. Auto-generated from the test suite.
GanttChart is a from-scratch Razor + minimal-JS engine shipping in Lumeo.Gantt.
Zero JS charting dependencies, zero CDN downloads, zero theming surprises. All MIT.
The original SVG-based Gantt component is still shipped for existing consumers —
see Legacy: Gantt (v2).
When to Use
- Planning a project with ordered phases, cross-task dependencies, and milestones.
- Grouping work by swim-lane, or modeling real parent/child hierarchy with a tree pane.
- Letting stakeholders see "what blocks what" at a glance, drag to reschedule, and drag the progress handle to mark completion.
- Enforcing your own scheduling rules — reject an invalid drag, or silently snap it to a grid — via the
OnTaskUpdatecommit gate.
Four phases (ParentId hierarchy, tree pane on automatically), cross-phase
Dependencies, and two IsMilestone diamonds — "Kickoff approved" and
"Go-live". This is also Day mode's own upper header row: the "W32 Aug 3 – 9"-style
bands are ISO-8601 week numbers, automatic in every Day-mode demo on this page.
OnTaskUpdate is a gate, not a notification: it can reject a drag
(false), accept it as proposed (true), or
accept it with an adjustment (GanttUpdateResult.AcceptWith(...)) that
snaps the committed Start/End to different values. Try dragging a bar here — this demo rejects
anything that would start before today, and snaps every other drop onto the nearest Monday.
Drag the progress handle at the base of a bar to change completion. OnProgressChange
fires the v2-parity notification; OnTaskUpdate (Source=Progress) gates the
same edit like every other gesture.
Use the built-in toolbar to switch between 7 zoom levels — QuarterDay, HalfDay,
Day, Week, Month, Quarter, and Year.
ShowZoomControl renders the segmented switcher shown here.
Pass a GroupBy function and/or set GroupLabel on each GanttTask.
Tasks are sorted by group and a section-header row is injected before the first task of each group.
Set GanttTask.ParentId to build real parent/child hierarchy. The tree pane shows the
task-name column plus any extra GanttTreeColumns you supply (here, a typed "Progress"
column) — drag the divider at the pane's trailing edge to resize it.
Set Readonly="true" to lock the chart — bars can't be dragged, resized, or created.
Use the BarColor delegate to return any CSS colour (hex, rgb, or a CSS variable like
var(--color-destructive)) per task — or set ColorByGroup="true" (shown in the
settings-menu demo above) for automatic per-group palette colouring.
NowIndicator draws a precise current-time line — only meaningful at sub-day
granularity, so this demo starts in HalfDay zoom (12-hour columns); switch to
Day/Week/etc. via the toolbar and it disappears, same as it would in your
own app. MarkOffDays tints weekend columns using OffDays (or a
region-appropriate default derived from the current culture) at any zoom level.
ShowRowCheckboxes renders a checkbox column in the tree pane. Only LEAF tasks are ever
members of SelectedIds — checking a parent phase toggles every descendant, not the
phase itself.
AllowRowReorder lets you drag rows in the tree pane to re-sequence a backlog. Reorder is
scoped to siblings under the same parent (or the root level here, since these tasks have none) —
OnRowReorder fires once per completed drop with the resolved before/after index.
RowTemplate replaces the tree pane's default name-cell content entirely — here, a
status dot (derived from Progress) plus the task name, instead of plain text.
API Reference
GanttChart
| Prop | Type | Default | Description |
|---|---|---|---|
| State | GanttState? | — | Hoists this chart's GanttState store (design spec Phase 3, T4 — the REUI useGanttState analog). null (the default) keeps the PRIOR, fully self-contained behavior: an internally-owned instance, constructed once at mount, invisible to anything outside this component — byte-identical to every pre-T4 render (see _state's own remarks for the mount-time resolution and GanttChartRenderTests's own byte-identical golden-markup regression). Supplying an instance lets a consumer read/react to this chart's state externally and drive it imperatively (ScrollToTaskAsync/ScrollToDateAsync/SetViewMode), and lets multiple components share ONE instance (see GanttState's own attach/detach remarks for exactly what "sharing" means: every attached-or-not subscriber re-renders on any change; only ONE at a time is the imperative-API target). Read ONCE, at OnInitialized — like ViewMode's own mount seed, this is an "owned outright for the component's lifetime" parameter, not a reactively-rebound one (design spec "keep it simple" precedent, same rationale _state's predecessor field already used). Changing WHICH instance is bound after mount is deliberately out of T4's scope — a live swap would need to rebuild every per-instance reconcile-tracking field (_lastSnapshot/_tasksVersion/_viewModeIntent/etc.) against the new instance's already-committed values, which is a different, harder problem T4 does not need to solve to satisfy the plan's own contract ("nav and view rendered separately against ONE shared state instance must work" — a stable instance for the component's lifetime, not a reactive one). |
| Tasks | IEnumerable<GanttTask>? | — | The tasks (and milestones) rendered as bars. |
| ViewMode | GanttViewMode | GanttViewMode.Day | The active zoom level. Two-way bindable via ViewModeChanged. Default Day. |
| Height | string | "500px" | CSS max-height of the timeline's scrollable viewport. Default "500px". |
| TodayHighlight | bool | true | Highlight today with an accent header label, header dot, and full-column tint (design spec Phase 3, T2 — master switch, see GanttTimeline.TodayHighlight's own remarks). Default true. |
| FirstDayOfWeek | DayOfWeek? | — | Explicit first-day-of-week override for the Day-mode ISO week-band upper header (design spec Phase 3, T2). Null derives it from CurrentCulture. |
| OffDays | IReadOnlySet<DayOfWeek>? | — | The week days treated as "off" for MarkOffDays (design spec Phase 3, T2). Null derives a region-appropriate set from CurrentCulture — see DefaultOffDays. |
| MarkOffDays | bool | false | Tints OffDays columns (design spec Phase 3, T2). Default false — no v2 equivalent. |
| NowIndicator | bool | false | Renders a precise current-time line in sub-day view modes (design spec Phase 3, T2). Default false. |
| BarHeight | int? | — | Override the pixel height of each task bar (default DefaultBarHeight). |
| ColumnWidth | int? | — | Override the pixel width of each date column for the current view mode. |
| BarColor | Func<GanttTask, string?>? | — | Per-task bar colour override — same contract as v2's Gantt.BarColor. |
| GroupBy | Func<GanttTask, string>? | — | Optional grouping function. When set, tasks are sorted by group label (stable within a group) before rendering — mirrors v2's GroupBy sort. Visual group-header rows are a tree-pane (T3) concern, not built here. |
| BarTemplate | RenderFragment<GanttTask>? | — | Replaces a bar's default label content when set. |
| ShowTreePane | bool? | — | Shows the left tree/list pane (design spec Phase 2, T3). Null (the default) resolves to DefaultShowTreePane: on when GroupBy is set or any task sets ParentId, off otherwise — a flat task list with neither gets the same single-pane layout T2 always rendered. |
| Readonly | bool | false | Disables drag-to-move/-resize editing in the chart (design spec Phase 2, T1 — v2-parity Readonly). Default false. |
| OnTaskUpdate | Func<GanttTaskUpdate, GanttUpdateResult>? | — | Unified commit GATE (Phase 4 — the REUI onEventUpdate analog, upgraded from a Phase 1-3 post-commit notification to a real pre-commit gate as part of this promotion; see GanttUpdateResult's own remarks for why this is the ONE breaking change the promotion ships). Called for EVERY drag/resize/progress/create edit — GanttTaskUpdateSource discriminates which gesture — BEFORE anything commits to this chart's state, so a consumer can: Reject (Reject/false): nothing commits — no state change, no OnDateChange/OnProgressChange/OnTaskCreate, no TasksChanged. Accept (Accept/true): commits exactly the proposed Task — byte-identical to every Phase 1-3 "no handler wired up" render. Accept with an adjustment (AcceptWith): commits the proposal with Start/End/Progress substituted in — e.g. snapping a drag to a host's own business-hour grid. Leaving this null (the default) accepts every edit unconditionally, exactly like the pre-promotion "no handler" case. Synchronous by design — mirrors CanDrop's own established sync-Func shape (this component's live-drag validator) so the two "should this happen" hooks share one calling convention. |
| CanDrop | Func<GanttTask, GanttScheduleDropContext, bool>? | — | Live scheduling drag-drop validator (design spec Phase 2, T2 — the REUI canDropEvent analog). See CanDrop for the full contract; passed straight through unchanged. |
| AllowCreate | bool | false | Opt-in drag-create on empty track background (design spec Phase 2, T3 — a REUI-parity addition; v2 has no drag-create at all, so this defaults to false, keeping strict v2 parity until a caller opts in). Passed straight through to AllowCreate — see there for the full contract, including why Readonly always wins over it. |
| ShowSummaryBars | bool | false | Renders a duration-weighted REUI-style envelope strip on every hierarchy-parent or flat-group row (design spec Phase 3, T3 — "Summary rollup bars, duration-weighted"). Default false — see ShowSummaryBars's own remarks for the reasoning (a deliberate v3-look delta from REUI's own default-on summaryBars, pinned in the Phase-3 ledger). |
| RollupMath | Func<IReadOnlyList<GanttTask>, GanttRollup>? | — | Overrides the default duration-weighted rollup math (design spec Phase 3, T3 — "Overridable rollup math"). See RollupMath for the full contract, including the defensive handling of a throwing or out-of-range override. Passed straight through, unchanged. |
| SummaryTemplate | RenderFragment<GanttRollup>? | — | Replaces a summary strip's default rendering when set (design spec Phase 3, T3 — the REUI renderSummary analog). Passed straight through to SummaryTemplate, unchanged. |
| ColorByGroup | bool | false | Assigns each task a stable per-group palette colour when BarColor is null (design spec Phase 3, T7). Passed straight through to ColorByGroup — see its own remarks for the full default-off reasoning and the group-colour stability contract. |
| ShowOffscreenIndicators | bool | true | Off-screen indicator chips at a row's edge, click-to-scroll (design spec Phase 3, T7). Passed straight through to ShowOffscreenIndicators — see its own remarks for the default-TRUE reasoning (the one deliberate exception to this campaign's default-off precedent for new REUI-parity look additions). |
| ShowZoomControl | bool | false | Floating +/- zoom control in the canvas corner (design spec Phase 3, T7). Passed straight through to ShowZoomControl. |
| ZoomLevels | IReadOnlyList<GanttViewMode>? | — | Restricts/orders which zoom levels BOTH GanttNav's toolbar switcher and ShowZoomControl's floating stepper offer (design spec Phase 3, T7 — fixes a pre-existing gap: GanttNav.ZoomLevels already existed but was never reachable through GanttChart's own surface before this task added a caller for it). Null (the default) shows the full default Day/Week/Month/Year set on both surfaces — see GanttZoomLevelModel for the exact resolution contract, including the Quarter-must-be-explicit rule. |
| WheelZoom | bool | true | Ctrl/Cmd + wheel over the timeline zooms, anchored on the pointer position (ReUI parity — wheelZoom, defaulting true). A bare wheel is never touched — the page/pane keeps scrolling exactly as it would with no Gantt on it. Steps through ZoomLevels (the SAME resolved list ShowZoomControl's stepper and GanttNav's toolbar already step through — never a second, independently-tunable list); at the resolved list's own zoom limits the gesture is left alone entirely, so the browser's own ctrl/cmd+wheel page-zoom takes over instead of the gesture being silently swallowed. See WheelZoom's own remarks for the full "stepped, not continuous" design rationale. |
| TreeColumns | IReadOnlyList<GanttTreeColumn>? | — | Extra tree-pane columns rendered after the pinned name column (design spec Phase 3, T5 — REUI "Multi-column task tree"). Null/empty (the default) renders exactly the single name column T2/T3 already did — byte-identical unless a caller opts in. See GanttTreeColumn for the per-column contract. |
| TreeHeaderMenu | RenderFragment? | — | Slot rendered in the tree pane's header row, after the column titles (design spec Phase 3, T5 — REUI "Columns menu in header slot"). Null (the default) renders nothing extra there. |
| RowTemplate | RenderFragment<GanttTask>? | — | Replaces a row's default label-cell content when set (design spec Phase 3, T5). The indent spacer and expander/toggle button are ALWAYS rendered by GanttTree regardless — see its own remarks for why that chrome must survive a custom template or the tree becomes unusable. Only applies to TASK rows (GanttVisibleRow.Task non-null); a flat-grouping GanttRowKind.GroupHeader row has no task to feed it and always falls back to its own label. |
| ShowTaskMeta | bool | false | Renders a second "date-range · N%" line under a tree row's name (2026-08-10 shadcn alignment plan, G6). Default false — see ShowTaskMeta's own remarks (this parameter is a straight pass-through). Ignored when RowTemplate is set. |
| TreePaneWidth | double? | — | Controlled width (pixels) of the tree pane's pinned name column (design spec Phase 3, T5 — the resizable tree/timeline splitter). Null (the default) is UNCONTROLLED: the chart tracks its own width internally, starting at DefaultTreePaneWidth and persisting whatever the user last dragged it to for the rest of the component's lifetime (session-persistent, not just per-render). Every value — controlled or internally tracked — is clamped to [MinTreePaneWidth, MaxTreePaneWidth]. Controlled-ness is decided by THIS parameter's own nullability — NOT by whether TreePaneWidthChanged has a delegate (a bug found during T5's own disable-check pass: a caller may wire the callback purely to OBSERVE resizes while leaving this null, which must still behave as fully uncontrolled/session-persistent, not silently freeze). See TreePaneWidthChanged's own remarks for the controlled-veto contract (same "the drag must not fight the parameter" discipline as every other controlled/uncontrolled pair in this component). |
| ShowRowCheckboxes | bool | false | Renders a checkbox column in the tree pane (design spec Phase 3, T6 — REUI "Leaf row selection (checkboxes)"). Default false. Passed straight through to ShowRowCheckboxes. |
| SelectedIds | ISet<string>? | — | Controlled set of selected LEAF task ids (design spec Phase 3, T6 — two-way SelectedIds). null (the default) is UNCONTROLLED: the chart tracks its own selection internally via SelectedIds, starting empty. Same controlled-ness discipline as TreePaneWidth (decided by THIS parameter's own nullability, not by whether SelectedIdsChanged has a delegate — a caller may wire the callback purely to OBSERVE selection while leaving this null). Only LEAF task ids are ever members — see GanttSelectionModel's own remarks for exactly which rows qualify and why a hierarchy-parent's own id never appears here even when every one of its descendants is selected. |
| AllowRowReorder | bool | false | Enables drag-to-reorder tree rows (design spec Phase 3, T6 — REUI "Row reorder (drag tree rows)"). Default false. Passed straight through to AllowRowReorder — see its own remarks for the within-bucket-only scope (cross-parent/cross-group moves are ParentId edits, out of scope, never offered by the drag UI). Readonly blocks it unconditionally. |
| CanDropRow | Func<GanttTask, GanttDropContext, bool>? | — | Live validation predicate for a candidate row-reorder drop position (design spec Phase 3, T6 — the REUI canDropEvent analog applied to row reorder; see GanttScheduleDropContext's own remarks for why SCHEDULING drag validation — CanDrop — is a separate, differently-shaped parameter/type from this one). Passed straight through to CanDropRow, unchanged. Null (default) permits every within-bucket position. |
| TrailingContent | RenderFragment? | — | Extra content rendered in GanttNav's toolbar, after the zoom switcher (design spec Phase 3, T8 — the GanttSettingsMenu companion's documented slot; v2 parity with Gantt.TrailingContent). Passed straight through to TrailingContent, unchanged. |
| DragGhostTemplate | RenderFragment<GanttTask>? | — | Overrides the drag ghost's rendered content for every bar (design spec Phase 3, T8). Passed straight through to DragGhostTemplate — see its own remarks (and DragGhostTemplate's) for the full coordinate-space contract. |
| BarContextMenu | RenderFragment<GanttTask>? | — | Renders a Lumeo ContextMenu at the pointer when a bar is right-clicked (design spec Phase 3, T8). Passed straight through to BarContextMenu — see its own remarks for the full contract, including why this is deliberately independent of Readonly. |
| InfiniteScroll | bool | true | Nearing either horizontal edge of the currently-rendered range extends it by another page, preserving scroll position, instead of dead-ending (design spec Phase 3, T9 — "horizontal infinite scroll ... replaces v2's fixed pad-before/after"). Passed straight through to InfiniteScroll — see its own remarks. Default true (per the design spec's own literal wording) — DELIBERATE, decided the same way ShowOffscreenIndicators's own default-true was: what does an EXISTING (pre-T9) v3 consumer actually notice? Nothing, for any chart that never scrolls far enough to reach the range ComputeInitialRange/ApplyPadding already computed — those two methods are COMPLETELY unchanged by this parameter, so the chart's INITIAL render, and every render before a user scrolls near either padded edge, is byte-identical regardless of this default. The only observable delta is what happens ONCE that edge is reached: pre-T9 (and false), the timeline simply stops — native scrollWidth caps there, exactly v2's own fixed-padding behavior; default-true, it keeps going. That is a strictly ADDITIVE capability (you can now scroll further than before), never a visual "look changed on upgrade with no code change" surprise the way e.g. NowIndicator/MarkOffDays' own opt-in defaults exist to avoid (both of those touch EVERY row/column of an already-fully-visible chart unconditionally the moment they're on; infinite scroll only ever engages in direct response to the viewer's own continued scrolling, past a boundary nothing before T9 let them reach anyway). false restores v2 parity exactly (byte-matching fixed padding — see the Phase-3 ledger entry for the parity spec that proves it against both routes). |
| Class | string? | — | Additional CSS classes merged onto the root element. |
| AdditionalAttributes | Dictionary<string, object>? | — | Captures unmatched attributes and applies them to the root element. |
Events
| TasksChanged | EventCallback<IEnumerable<GanttTask>> | Raised whenever the task set changes — either because Tasks genuinely changed, or because the user dragged/resized a bar in the chart (design spec Phase 2, T1 — v2-parity TasksChanged, same contract as v2's Gantt.TasksChanged). |
| ViewModeChanged | EventCallback<GanttViewMode> | Raised when the user picks a new zoom level from GanttNav's toolbar. |
| OnTaskClick | EventCallback<GanttTask> | Raised when a bar is activated via click or keyboard (Enter/Space) — same contract as v2's Gantt.OnTaskClick (Codex round 4, P2 #5). |
| OnDateChange | EventCallback<GanttTask> | Raised after a task bar's start/end dates change via drag/resize, with the updated task (design spec Phase 2, T1 — v2-parity OnDateChange, same contract as v2's Gantt.OnDateChange). Also folded into TasksChanged. |
| OnProgressChange | EventCallback<GanttTask> | Raised after a task bar's progress changes via the progress handle (design spec Phase 2, T2 — v2-parity OnProgressChange, same contract as v2's Gantt.OnProgressChange). Also folded into TasksChanged. |
| OnTaskCreate | EventCallback<GanttTaskUpdate> | Raised when a drag-create commits (design spec Phase 2, T3 — REUI parity; v2 has no equivalent). Fired alongside OnTaskUpdate (Source=Create) — mirrors how OnProgressChange/OnDateChange are each raised alongside OnTaskUpdate for their own gesture, per source, in HandleTaskUpdateAsync. Also folded into TasksChanged (the new task is APPENDED, not merged-by-id, for this source). |
| TreePaneWidthChanged | EventCallback<double> | Raised exactly once per completed splitter gesture (a pointer-drag release, or a keyboard nudge — never continuously while dragging; see GanttTree's own remarks for why the LIVE drag visual is JS-only and never round-trips through Blazor mid-gesture) with the new width, already clamped. Fires whenever a delegate is present, whether or not TreePaneWidth itself is set — notification and control are separate concerns (mirrors OnProgressChange/OnDateChange firing independent of TasksChanged). Control specifically is decided by TreePaneWidth's own nullability: when it is non-null (CONTROLLED) and the consumer does not update its own bound value in response to this callback, that is a veto — the NEXT render reverts the pane to whatever TreePaneWidth still holds, the same veto contract TasksChanged/ViewModeChanged already have. The drag never "fights" the parameter, because nothing round-trips through Blazor until the gesture is already over. |
| SelectedIdsChanged | EventCallback<ISet<string>> | Raised whenever the selection changes (a checkbox click — leaf toggle or parent/group select/deselect-descendants), with the FULL resulting set. Fires whenever a delegate is present, whether or not SelectedIds itself is set — notification and control are separate concerns (mirrors TreePaneWidthChanged's own remarks). Control specifically is decided by SelectedIds's own nullability: when non-null (CONTROLLED) and the consumer does not update its own bound value in response, that is a veto — the NEXT render reverts to whatever SelectedIds still holds. |
| OnRowReorder | EventCallback<GanttRowReorder> | Raised once, after a completed row-reorder drop, with the resolved GanttRowReorder payload (design spec Phase 3, T6). This component ALSO applies the equivalent list reorder to its own task set and pushes it through the SAME TasksChanged/controlled- veto pipeline every other edit (move/resize/progress/create) already uses — see HandleRowReorderAsync's own remarks for why the component mutates rather than merely reporting. |
GanttTask
| Field | Type | Description |
|---|---|---|
| Id | string | Stable identifier used to reconcile drag edits back into the caller's collection. |
| Name | string | The label rendered on the task bar and in the tree pane's name column. |
| Start | DateTime | Start date (inclusive). |
| End | DateTime | End date. |
| Progress | int | Completion percentage, 0–100. Drives the progress fill and the progress-handle drag. |
| Dependencies | string[]? | Ids of tasks this task depends on. Rendered as routed arrows between bars. |
| CustomClass | string? | Extra CSS class applied to the bar — use to colour-code milestones or delayed work. |
| IsMilestone | bool | When true, renders as a diamond at the Start date. End is clamped to Start. |
| GroupLabel | string? | Optional swim-lane label — cluster with GroupBy. |
| ParentId | string? | GanttChart-only: set to another task's Id to build real parent/child hierarchy and enable the tree pane by default. |
| IsRecurring | bool | Marks the bar as part of a series you generated yourself — see Styling hooks' data-recurring. |
GanttUpdateResult / GanttUpdateAdjustment
The verdict OnTaskUpdate returns for every drag/resize/progress/create commit.
| Value | Effect |
|---|---|
| false / GanttUpdateResult.Reject | Rejects the edit outright — nothing commits, no callback fires, no TasksChanged. |
| true / GanttUpdateResult.Accept | Commits exactly the proposed Start/End/Progress. |
| GanttUpdateResult.AcceptWith(adjustment) | Commits adjustment's Start/End/Progress instead of the raw proposal (any left null keeps the proposed value). |
GanttViewMode
| Value | Description |
|---|---|
| QuarterDay | 6-hour columns — useful for hour-scale shift scheduling. |
| HalfDay | 12-hour columns. |
| Day | One column per day — the default. |
| Week | One column per week. |
| Month | One column per month — best for multi-quarter plans. |
| Quarter | One column per 3-month quarter — GanttChart-only. |
| Year | One column per year — best for roadmap-style views. |
Styling hooks (data-*)
A rendered bar, grid cell, or drag ghost carries plain data-*
attributes for its own state — no class-string parsing required. Boolean states are presence-only
(the attribute is either there or it isn't, so [data-selected]
is a valid selector on its own); data-progress
is the one exception and carries the clamped 0–100 value.
| Attribute | Element | Shape | Meaning |
|---|---|---|---|
| data-selected | Bar | presence | The task's id is in SelectedIds (e.g. checked via a row checkbox). |
| data-dragging | Bar | presence | A move/resize/progress drag gesture is live on this bar (set by the drag engine, not by a re-render). |
| data-progress | Bar | 0–100 | The task's clamped completion percentage — the same value the progress fill's width uses. |
| data-completed | Bar | presence | Progress is exactly 100. |
| data-past | Bar | presence | The task's End is before "now" (the browser's current time, not the server's). |
| data-recurring | Bar | presence | GanttTask.IsRecurring is set — for a consumer generating its own repeating occurrences. |
| data-off | Header cell / canvas band | presence | An off-day column under MarkOffDays/OffDays — on the header date cell and the canvas tint behind it. |
| data-drop-invalid | Drag ghost | presence | The current drag position fails CanDrop — including while a verdict is still in flight (fail-closed). |
One example per family — a boolean bar state, the value-carrying one, and the grid/drag hooks:
/* Boolean bar state — outline the selected task */
[data-selected] { outline: 2px solid var(--color-primary); }
/* Value-carrying — a completion badge past 80% */
[data-progress] { --p: attr(data-progress number); }
[data-progress]:is([data-completed]) { opacity: 0.7; }
/* Grid / drag ghost */
[data-off] { background: color-mix(in oklab, var(--color-muted) 60%, transparent); }
[data-drop-invalid] { border-color: var(--color-destructive); }Related Components
- Scheduler — calendar-style event grid when you care about time-of-day rather than project phases.
- Data Grid — tabular view of the same task data when a timeline isn't needed.
- Gantt (Legacy) — the original SVG-based engine, kept for backward compatibility.