Gantt
A horizontal roadmap / Gantt canvas — entity rows × a zoomable time axis with editable bars, milestones, rolled-up summaries, dependency arrows and a critical path.
@garn/ui/ganttDefault
The hero: a grouped roadmap with milestones, progress, snap, a minimap, hover detail and a full drag editor with a useGantt scale/zoom toolbar.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Overview
Roadmap
The hero: a grouped roadmap with milestones, progress, snap, a minimap, hover detail and a full drag editor with a useGantt scale/zoom toolbar.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Flat
No groups, day scale — a simple task list with weekend shading and milestones.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Dependencies
Dependencies
FS/SS dependency arrows chaining into a milestone, a violation flagged, weighted group rollups, and interactive drag-to-link.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Critical Path
The zero-float critical path highlighted, with a floating side task showing its total float as a trailing bar.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Planning
Baseline Health
Planned-vs-actual baselines (ghost bars) + schedule-health tones (behind / overdue) around today's line.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Rail
Wbs Rail
The left rail as a WBS grid — nested subtasks, extra columns, a resizable divider, inline rename and drag-to-reorder.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Lenses
Lenses
Re-group by team or status, filter to active, and spotlight a dependency chain on hover.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Interaction
External Drop
Drag an unscheduled chip from outside onto a row to schedule it (onExternalDrop).
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
States
The imperative apiRef (today / fit / expand / collapse) plus the loading and empty states.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Appearance
Tones
The bar tone × appearance range (soft / solid), plus a summary and a milestone.
Use the arrow keys to move between bars. Hold Shift with the left or right arrow to reschedule the focused bar, or Alt with the left or right arrow to resize it. Press the left or right bracket key to jump to a bar's predecessor or successor. Press Escape to cancel a drag.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add gantt- Registry
gantt- Source
- gantt.tsx
- Deps
class-variance-authoritylucide-react@radix-ui/react-slot- Registry deps
utilsuse-controllable-stateuse-announceuse-isomorphic-layout-effectdrag-gestureinline-editemptyskeletonhover-card
Import
import { Gantt } from "@garn/ui/gantt";Props
Generated from the component's TypeScript types.
apiRef- Description
- An imperative handle for driving the gantt from outside a child.
- Type
React.Ref<GanttApi>
collapsedGroups- Description
- Controlled/uncontrolled collapsed group ids.
- Type
string[]
criticalPath- Description
- Highlight the critical path (the zero-float dependency chain) + show each non-critical task's total float as a faint trailing bar. Needs `dependencies`.
- Type
boolean
defaultCollapsedGroups- Description
- No description.
- Type
string[]
defaultRange- Description
- No description.
- Type
GanttRange
defaultScale- Description
- No description.
- Type
GanttScale
defaultSelectedId- Description
- No description.
- Type
string
dependencies- Description
- Scheduling links drawn as arrows between bars (cycle-safe; see GanttDependency).
- Type
GanttDependency[]
editable- Description
- No description.
- Type
boolean
empty- Description
- What to render when there are no rows (default: a built-in empty state).
- Type
React.ReactNode
filter- Description
- Hide rows that don't match; rollups stay honest over the visible set.
- Type
(item: T) => boolean
firstDay- Description
- Week start (0=Sun…6=Sat); defaults to the locale's convention, else Sunday.
- Type
number
groupBy- Description
- Re-group rows by a derived key (status, assignee, …), overriding `group`. Return a group `{ id, label, tone }`, a bare id string, or null for ungrouped.
- Type
(item: T) => { id: string; label: string; tone?: GanttTone; } | string | null
groups- Description
- No description.
- Type
GanttGroup[]
isWorkingDay- Description
- Predicate for column shading (day scale). Default: weekends are non-working.
- Type
(date: Date) => boolean
itemsRequired- Description
- No description.
- Type
T[]
loading- Description
- Show a skeleton loading state instead of the rows.
- Type
boolean
locale- Description
- No description.
- Type
string
minimap- Description
- Show a minimap / overview strip below the canvas with a draggable viewport.
- Type
boolean
onCollapsedGroupsChange- Description
- No description.
- Type
(ids: string[]) => void
onDependencyCreate- Description
- Enable interactive drag-to-link — hovering a bar shows edge handles you drag onto another bar to create a dependency. Fired only for cycle-safe links.
- Type
(dep: Required<Pick<GanttDependency, "from" | "to" | "type">>) => void
onExternalDrop- Description
- Accept a native (HTML5) drag-in onto a row; fires with the drop target.
- Type
(info: { row: string | null; start: Date; end: Date; }) => void
onItemClick- Description
- No description.
- Type
(item: T) => void
onItemMove- Description
- No description.
- Type
(item: T, range: GanttRange) => void
onItemProgress- Description
- No description.
- Type
(item: T, progress: number) => void
onItemRename- Description
- Enable inline rename of a row's label in the rail (composes InlineEdit).
- Type
(item: T, label: string) => void
onItemResize- Description
- No description.
- Type
(item: T, range: GanttRange) => void
onRangeChange- Description
- No description.
- Type
(range: GanttRange) => void
onReorder- Description
- Enable dragging a rail row to reorder / reparent it. Fires with the drop target: the new parent (or null for top level) and index among its children.
- Type
(item: T, target: { parent: string | null; index: number; }) => void
onScaleChange- Description
- No description.
- Type
(scale: GanttScale) => void
onSelectedChange- Description
- No description.
- Type
(id: string | null) => void
onVisibleRangeChange- Description
- Fired as the canvas scrolls horizontally (the visible-day range). Named so it never shadows the native `onScroll` DOM prop (different signature).
- Type
(info: { startDay: number; endDay: number; }) => void
railColumns- Description
- Extra left-rail columns (turns the rail into a mini WBS grid).
- Type
GanttRailColumn<T>[]
range- Description
- No description.
- Type
GanttRange
ref- Description
- No description.
- Type
React.Ref<HTMLDivElement>
renderDetail- Description
- Rich detail shown in a HoverCard when a bar is hovered/focused.
- Type
(item: T) => React.ReactNode
renderItem- Description
- No description.
- Type
(item: T, ctx: GanttItemContext) => React.ReactNode
resizableRail- Description
- Let the user drag the rail / canvas divider to resize the rail.
- Type
boolean
scale- Description
- No description.
- Type
GanttScale
scheduleHealth- Description
- Tone each task by schedule health (behind / overdue) from its progress vs today.
- Type
boolean
scrollToNow- Description
- Scroll the canvas near today on mount (instead of opening at the span start).
- Type
boolean
selectable- Description
- No description.
- Type
boolean
selectedId- Description
- No description.
- Type
string
showWeekNumbers- Description
- Show ISO week numbers on the week-scale header tier.
- Type
boolean
snap- Description
- During a drag, snap the moving edge to nearby bar edges / today / milestones.
- Type
boolean
spotlight- Description
- On hover, highlight a bar's full dependency chain and dim the rest.
- Type
boolean
todayLine- Description
- No description.
- Type
boolean
Plus 277 inherited native <div> attributes.
GanttBar
asChild- Description
- No description.
- Type
boolean
linkable- Description
- Show the dependency link handles (Gantt sets this from `onDependencyCreate`).
- Type
boolean
onLinkStart- Description
- No description.
- Type
(edge: "start" | "end", e: React.PointerEvent) => void
onProgressStart- Description
- No description.
- Type
(e: React.PointerEvent) => void
onResizeStart- Description
- No description.
- Type
(edge: "start" | "end", e: React.PointerEvent) => void
progressHandle- Description
- Show the progress drag handle at the fill edge.
- Type
boolean
ref- Description
- No description.
- Type
React.Ref<HTMLButtonElement>
resizable- Description
- Show start/end resize handles (Gantt sets this from `editable`).
- Type
boolean
Plus 291 inherited native HTML attributes.
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="gantt"][data-slot="gantt-rail"][data-slot="gantt-rail-header"][data-slot="gantt-row-header"][data-slot="gantt-canvas"][data-slot="gantt-header"][data-slot="gantt-body"][data-slot="gantt-grid"][data-slot="gantt-today"][data-slot="gantt-deps"][data-slot="gantt-row"][data-slot="gantt-bar"][data-slot="gantt-summary"][data-slot="gantt-milestone"][data-slot="gantt-empty"][data-slot="gantt-minimap"]| Property | Token | Tier |
|---|---|---|
dayWidth | --garn-gantt-day-base | component · identity |
rowHeight | --garn-gantt-row | component · identity |
railWidth | --garn-gantt-rail | component · identity |
railColumnWidth | --garn-gantt-col | component · identity |
headerTier | --garn-gantt-tier | component · identity |
viewport | --garn-gantt-viewport | component · identity |
surface | --garn-background | semantic |
hairline | --garn-border | semantic |
todayLine | --garn-danger-solid | semantic |
selectedRing | --garn-brand-solidbrandable | semantic |
Private instance vars (never externalize): --garn-gantt-day--garn-gantt-rail--gantt-x--gantt-w--gantt-p--b-bg--b-fg--b-solid--b-solid-fg--b-line--b-fill--b-rail
Overview
Gantt is a roadmap canvas — entity rows on the left, a zoomable time axis across the top, and a horizontal bar per item from its start to its end. Because each item owns a row, there is no overlap packing; the axis stretches from a day view out to years. Zero-duration items render as milestone diamonds, and a group or parent task rolls its children into a summary bar with duration-weighted progress. It is a full editor: drag a bar to reschedule, drag its edges to resize, drag the progress handle — by pointer or keyboard — with optimistic callbacks, so you always own items. On top of the core sit the planning layers: dependencies draw cycle-safe FS/SS/FF/SF arrows (with drag-to-link), criticalPath runs a backward-pass CPM to ring the zero-float chain and show each task's float, baseline + scheduleHealth compare planned vs actual, and groupBy / filter / spotlight re-lens the same data. For a vertical read-only history use timeline; for an hour-grid calendar use scheduler.
Guidelines
Own the data; treat the callbacks as intents. items is a controlled data prop — onItemMove / onItemResize / onItemProgress / onReorder / onDependencyCreate hand you the proposed change; apply it to your own state. Nothing moves until you do.
Style only through tone × appearance and the render slot. Give bars a tone (the shared vocabulary) and appearance (solid / soft / outline); reach for renderItem when you need custom bar content and renderDetail for a hover peek. Geometry rides inline CSS variables in day-units — never hand-position or hardcode a color or a pixel length.
Keep it operable and honest. The keyboard model is the differentiator — arrow-navigate bars, Enter to select, Shift/Alt+arrows to reschedule/resize, [ and ] to walk dependencies; don't strip the focus ring or the sr-only instructions. Pass range/defaultRange (or a fixed anchor for your dates) for SSR-deterministic output.
Best practices
- Keep
itemsin your own state and apply the move / resize / progress / reorder / link callbacks optimistically. - Model phases as
groupsor parent tasks (viaparent) so children roll up into a weighted summary bar. - Add
dependenciesfor a real Gantt; turn oncriticalPath(with deps) to surface the driving chain and float. - Give a bar a
baselineand turn onscheduleHealthto compare planned vs actual around the today-line. - Pass
range/defaultRange(or anchor your dates) for SSR-deterministic output; build a toolbar fromuseGanttor drive it viaapiRef.
- Don't mutate the passed items in a callback and expect a re-render — update your source of truth.
- Don't hardcode bar colors or positions — use
tone/appearanceand let the canvas place them in day-units. - Don't remove the composed bar accessible name inside a custom
renderItem. - Don't expect garn to reschedule for you — dependencies and the critical path are display-only (garn flags violations, never moves bars).
Accessibility
- Role
group- ARIA APG
- grid
- Focus
- Roving tabindex over the bars (and focusable group/parent summary bars); focus-visible rings preserved; keyboard focus follows [ / ] dependency jumps.
Enforceable contracts
The root exposes an accessible name describing the visible range (default: "Roadmap, {range}").
when always
Every focusable bar control has a composed accessible name including its label and dates; a custom renderItem must not remove it.
when a bar is rendered
The grid, header, today-line, minimap and dependency arrows are aria-hidden; a11y lives on the bars + the [ / ] jump.
when always
The collapse control is a button with aria-expanded and a label naming the group/task.
when a group or parent row is rendered
Keyboard
| Tab | Enter the bars; one bar is tabbable (roving tabindex). |
| ArrowDownArrowRight | Move to the next bar (down the rows). |
| ArrowUpArrowLeft | Move to the previous bar. |
| HomeEnd | First / last bar. |
| EnterSpace | Select / activate the focused bar (onItemClick). |
| ShiftArrowLeft | Reschedule the focused bar one day earlier (when editable). |
| ShiftArrowRight | Reschedule the focused bar one day later (when editable). |
| AltArrowLeft | Shorten the focused bar by one day (when editable). |
| AltArrowRight | Lengthen the focused bar by one day (when editable). |
| [ | Jump to the focused bar's predecessor (a dependency source). |
| ] | Jump to the focused bar's successor. |
| Escape | Cancel an in-progress pointer drag / link / reorder (no commit). |
- • The root is role=group with an accessible name for the visible range (aria-roledescription="gantt chart") and an sr-only aria-describedby that states the keyboard model.
- • The decorative grid, time header, today-line, minimap and dependency arrows are presentational (aria-hidden); the real semantics live on the bars.
- • Each bar is a focusable control with a composed accessible name ("{label}, {start} to {end}, {n}% complete"); a milestone reads "{label} milestone, {date}", a summary "{label} summary, …".
- • The name also folds in relationships + status: "after {preds}", "blocks {succs}", "on the critical path", "overdue" / "behind schedule", and the baseline range.
- • One bar is tabbable at a time (roving tabindex); selection is exposed via aria-current="true" on the selected bar (not visual-only).
- • Group / parent rows have an aria-expanded collapse toggle labelled "Expand/Collapse {label}". The rail resize divider is a labelled role=separator with aria-value min/max/now; the minimap is a role=scrollbar with aria-controls the scroll region.
- • Move / resize / progress / link / reorder results are announced in a polite live region; a cancelled drag announces "Cancelled."
- • Geometry uses logical properties, so the rail, bars and arrows mirror under dir="rtl".
- Status
- Beta
- Version
v0.3.0- Introduced
v0.2.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.