Marble UI
← Components

Gantt

Preview: Project

Timeline of tasks with progress, milestones, dependency arrows and a today line. The task list is a table for assistive tech.

Import

import { Gantt } from '@marbleui/ui'

Props

<Gantt>

PropTypeDefaultDescription
aria-label *requiredstring—
tasks *requiredGanttTask[]—
classNamestring—
dayWidthnumber28Pixels per day.

Usage notes

Gantt draws a read-only project timeline beside a task table: bars with progress, milestones, dependency arrows, weekend shading and a today line. For meetings and dated events use EventCalendar; for a history of events use Timeline.

<Gantt
  aria-label="Website redesign schedule"
  tasks={[
    { id: 'research', name: 'User research', start: new Date(2026, 9, 1), end: new Date(2026, 9, 9), progress: 100, color: 3 },
    { id: 'build', name: 'Build components', start: new Date(2026, 9, 12), end: new Date(2026, 9, 26), progress: 40, dependsOn: ['research'] },
    { id: 'launch', name: 'Launch', start: new Date(2026, 9, 28), end: new Date(2026, 9, 28), milestone: true, dependsOn: ['build'] },
  ]}
/>
  • Rows follow the order of tasks, and the date range is fitted to them. Tasks with a group are listed under a header row per group (groups in order of first appearance, ungrouped tasks first); in the table each group is its own row group. dayWidth (pixels per day, default 28) sets the zoom. Milestones sit on their end date; color picks --chart-1…8.
  • aria-label names the table, which is what assistive tech gets: each task’s name and due date. Start dates, progress and dependencies are only drawn, with a tooltip on pointer hover, so put anything essential in the name.
  • The timeline is a focusable scroll region that keyboard users can scroll with the arrow keys. There’s no drag to reschedule and no click handler. Pass at least one task.
Example source · show
import { useMemo } from 'react'
import { Gantt, type GanttTask } from './gantt'

function useTasks() {
  return useMemo(() => {
    const n = new Date()
    const d = (o: number) => new Date(n.getFullYear(), n.getMonth(), n.getDate() + o)
    const t: GanttTask[] = [
      { id: 'research', name: 'User research', start: d(-12), end: d(-4), progress: 100, color: 3 },
      { id: 'wire', name: 'Wireframes', start: d(-5), end: d(2), progress: 70, color: 1, dependsOn: ['research'] },
      { id: 'visual', name: 'Visual design', start: d(1), end: d(9), progress: 15, color: 7, dependsOn: ['wire'] },
      { id: 'build', name: 'Build components', start: d(4), end: d(18), progress: 5, color: 2, dependsOn: ['wire'] },
      { id: 'qa', name: 'QA & accessibility', start: d(16), end: d(22), color: 5, dependsOn: ['build', 'visual'] },
      { id: 'launch', name: 'Launch', start: d(23), end: d(23), milestone: true, color: 8, dependsOn: ['qa'] },
    ]
    return t
  }, [])
}

export const styles = {
  Project: () => <Gantt aria-label="Website redesign schedule" tasks={useTasks()} className="w-[52rem]" />,
  Grouped: () => {
    const tasks = useTasks()
    const phase: Record<string, string> = { research: 'Discovery', wire: 'Design', visual: 'Design', build: 'Delivery', qa: 'Delivery', launch: 'Delivery' }
    return <Gantt aria-label="Website redesign schedule by phase" tasks={tasks.map((t) => ({ ...t, group: phase[t.id] }))} className="w-[52rem]" />
  },
  Compact: () => <Gantt aria-label="Website redesign schedule" tasks={useTasks()} dayWidth={16} className="w-[40rem]" />,
}

Source: packages/ui/src/gantt/gantt.tsx

Catalog

Category
date-time
Priority
Pro
Primitive
custom