Gantt
Gantt chart with grouped features, lanes, drag-to-move and edge resize, long-press add, date markers, weekend shading, and zoom control.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-ganttpnpm add react-native-reanimated react-native-gesture-handler date-fnsUsage
import { GanttView, type GanttGroup } from '@/components/docyrus-native/gantt';
const groups: GanttGroup[] = [
{
id: 'frontend',
name: 'Frontend',
features: [
{
id: 'f1',
name: 'Auth Flow',
startAt: new Date(2026, 2, 1),
endAt: new Date(2026, 2, 15),
status: { id: 'done', name: 'Done', color: '#22c55e' }
}
]
}
];
<GanttView
groups={groups}
range="monthly"
showToday
onFeatureMove={(id, startAt, endAt) => save(id, startAt, endAt)}
onFeaturePress={(feature) => console.log(feature.name)} />GanttView is a monolithic adaptation of the web compound Gantt (GanttProvider, GanttSidebar*, GanttFeatureRow, GanttAddFeatureHelper, GanttCreateMarkerTrigger, …). The compound pieces are too granular for React Native, so the same capabilities are exposed as props and render props on one component.
Lanes
Features of the same group that share a lane are packed into one row — overlapping features stack into sub-rows, exactly like web GanttFeatureRow. Features without a lane get a row of their own. The sidebar shows a lane row with the lane name, its item count, and the duration of the whole lane.
const features = [
{ id: 'b1', name: 'API', startAt, endAt, status, lane: 'Sprint work' },
{ id: 'b2', name: 'Migration', startAt: later, endAt: evenLater, status, lane: 'Sprint work' }
];Adding items and markers
Hover affordances from web become long-presses:
- Long-press an empty timeline cell →
onAddItem(date, info)(webGanttAddFeatureHelper).infocarries the pressedgroupandrow(so you can put the new feature into the same lane). Presses that land on a bar are ignored. - Long-press the timeline header →
onCreateMarker(date)(webGanttCreateMarkerTrigger). - Tap a marker label → an action sheet with Remove marker →
onMarkerRemove(id)(web right-click context menu). WithoutonMarkerRemovethe label is not interactive.
<GanttView
groups={groups}
markers={markers}
onAddItem={(date, { group, row }) => createFeature(group?.id, date, row?.lane)}
onCreateMarker={(date) => setMarkers(m => [...m, { id: uid(), date, label: 'Milestone' }])}
onMarkerRemove={(id) => setMarkers(m => m.filter(x => x.id !== id))} />Custom content
<GanttView
groups={groups}
renderFeature={(feature) => (
<Text className="text-[10px] font-semibold text-white">{feature.name} · {feature.status.name}</Text>
)}
renderSidebarItem={(row) => (
<Text className="text-xs text-foreground">{row.lane ?? row.features[0]?.name}</Text>
)} />Interactions
| Gesture | Result |
|---|---|
| Tap a bar | onFeaturePress(feature) |
| Long-press (200 ms) + drag a bar horizontally | Live preview, then onFeatureMove(id, startAt, endAt) (duration kept) |
| Long-press (100 ms) + drag a bar's left / right edge | Live resize preview, then onFeatureMove with the new start / end |
| Tap a sidebar row | Scrolls to the (earliest) feature, then onSelectItem(id) + onFeaturePress(feature) |
| Long-press an empty cell | onAddItem(date, info) |
| Long-press the header | onCreateMarker(date) |
| Tap a marker label | Remove-marker action sheet → onMarkerRemove(id) |
Drag and resize are enabled only when onFeatureMove is supplied and readOnly is false. readOnly also disables long-press add and marker create / remove.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
groups | GanttGroup[] | — | Feature groups (required) |
range | 'daily' | 'monthly' | 'quarterly' | 'monthly' | Timeline range mode |
zoom | number | 100 | Zoom level percentage (scales the column width) |
markers | GanttMarkerData[] | — | Date markers on the timeline |
onFeatureMove | (id: string, startAt: Date, endAt: Date) => void | — | Drag-to-move / edge-resize callback. Enables drag + resize |
onFeaturePress | (feature: GanttFeature) => void | — | Feature bar or sidebar row tap |
onSelectItem | (id: string) => void | — | Sidebar row tap — feature id (a lane row passes its earliest feature) |
onAddItem | (date: Date, info: GanttAddItemInfo) => void | — | Long-press an empty timeline cell |
onCreateMarker | (date: Date) => void | — | Long-press the timeline header |
onMarkerRemove | (id: string) => void | — | Remove-marker action (tap a marker label) |
renderFeature | (feature: GanttFeature) => ReactNode | — | Custom content inside each feature bar (replaces the name label) |
renderSidebarItem | (row: GanttRowData, group: GanttGroup) => ReactNode | — | Custom sidebar row content (replaces dot + name + duration) |
isColumnSecondary | (date: Date) => boolean | weekends | Daily range only — shades matching day columns |
readOnly | boolean | false | Disables drag, resize, long-press add, and marker create / remove |
showToday | boolean | true | Show the today line |
className | string | — | Additional classes for the root view |
style | ViewStyle | — | Style override for the root view |
GanttGroup
| Field | Type | Description |
|---|---|---|
id | string | Unique group identifier |
name | string | Group display name |
features | GanttFeature[] | Features in this group |
GanttFeature
| Field | Type | Description |
|---|---|---|
id | string | Unique feature identifier |
name | string | Feature display name |
startAt | Date | Start date |
endAt | Date | End date |
status | GanttStatus | Status with id, name, and color (bar color) |
lane | string | Optional lane id — features sharing a lane share one row |
GanttStatus
| Field | Type | Description |
|---|---|---|
id | string | Status identifier |
name | string | Status display name |
color | string | Status bar color |
GanttMarkerData
| Field | Type | Description |
|---|---|---|
id | string | Marker identifier |
date | Date | Marker date |
label | string | Marker label |
color | string | Marker line color (optional, defaults to primary) |
GanttRowData
| Field | Type | Description |
|---|---|---|
id | string | feature.id for a lane-less row, lane:<lane> for a lane row |
lane | string | Lane id when the row is a lane row |
features | GanttFeature[] | Row features, sorted by startAt |
GanttAddItemInfo
| Field | Type | Description |
|---|---|---|
group | GanttGroup | Group under the finger (undefined below the last group) |
row | GanttRowData | Row under the finger (undefined on a group header row) |
Translations
| Key | Fallback |
|---|---|
ui.gantt.issues | Issues |
ui.gantt.duration | Duration |
ui.gantt.itemCount | {count} items |
ui.gantt.removeMarker | Remove marker |
ui.common.cancel | Cancel |
Durations use date-fns formatDistance (same as web). Marker dates use useDateFormat().formatDate when a DateFormatProvider is mounted, else MMM dd, yyyy.
Components
| Component | Description |
|---|---|
GanttView | Main Gantt chart component |
Type Exports
| Type | Description |
|---|---|
GanttViewProps | Props for GanttView |
GanttGroup | Group with features |
GanttFeature | Individual timeline feature |
GanttStatus | Feature status definition |
GanttMarkerData | Timeline date marker |
GanttMarkerProps | Web-parity alias of GanttMarkerData |
GanttRowData | A rendered row (single feature or lane) |
GanttAddItemInfo | Second argument of onAddItem |
GanttRange | Range mode type |
Range | Web-parity alias of GanttRange |