<gantt-chart>
HTML-first Gantt chart using native table, progress, and time elements with progressive enhancement to an interactive timeline.
Overview
A web component that progressively enhances a semantic <table> into an interactive Gantt timeline. Without JavaScript, users see a fully readable data table with native <progress> bars and <time> elements. With JavaScript, the component renders a visual timeline with positioned bars, swim lane groups, milestones, progress fill, and a today marker.
<gantt-chart title="Q2 Roadmap" show-today show-progress> <table> <thead> <tr><th>Task</th><th>Start</th><th>End</th><th>Progress</th></tr> </thead> <tbody> <tr data-task-id="design" data-group="Design" data-status="in-progress"> <td>Design system</td> <td><time datetime="2026-04-01">Apr 1</time></td> <td><time datetime="2026-04-15">Apr 15</time></td> <td><progress value="75" max="100">75%</progress></td> </tr> <tr data-task-id="api" data-group="Engineering" data-depends="design"> <td>API development</td> <td><time datetime="2026-04-10">Apr 10</time></td> <td><time datetime="2026-04-25">Apr 25</time></td> <td><progress value="30" max="100">30%</progress></td> </tr> <tr data-task-id="launch" data-depends="api" data-milestone> <td>Launch</td> <td><time datetime="2026-05-01">May 1</time></td> <td><time datetime="2026-05-01">May 1</time></td> <td><progress value="0" max="100">0%</progress></td> </tr> </tbody> </table></gantt-chart>
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
title | string | — | Chart heading |
src | string (URL) | — | Path to JSON task data |
view | string | "auto" | Timeline granularity: auto, day, week, month, quarter |
show-today | boolean | — | Show vertical today marker |
show-progress | boolean | — | Show progress fill inside bars |
show-dependencies | boolean | — | Draw dependency arrows between tasks |
compact | boolean | — | Reduced row height and spacing |
Task Row Attributes
Each <tr> in the table body can carry these data attributes to configure the corresponding task bar.
| Attribute | Type | Description |
|---|---|---|
data-task-id | string | Unique task identifier |
data-group | string | Swim lane group (tasks with the same group are visually clustered) |
data-depends | string | Comma-separated task IDs this task depends on |
data-status | string | not-started, in-progress, done, or blocked |
data-assignee | string | Assignee name |
data-milestone | boolean | Renders a diamond marker instead of a duration bar |
data-color | string | Custom bar color (CSS color value) |
How It Works
The component reads task data from the semantic table structure:
- Task name — from the first
<td>text - Start/end dates — from
<time datetime="...">elements - Progress — from
<progress value="..." max="..."> - Metadata — from
data-*attributes on the<tr>
Each bar is positioned at left: (taskStart - rangeStart) / rangeTotal * 100% with width: (taskEnd - taskStart) / rangeTotal * 100%. The timeline granularity auto-adjusts based on the date range, or can be set explicitly via the view attribute.
Data Mode
Load task data from JSON via the src attribute. The component creates the semantic table internally.
{ "title": "Q2 Roadmap", "tasks": [ { "id": "design", "name": "Design system", "start": "2026-04-01", "end": "2026-04-15", "progress": 75, "group": "Design", "status": "in-progress" }, { "id": "launch", "name": "Launch", "start": "2026-05-01", "end": "2026-05-01", "milestone": true, "depends": ["design"] } ]}
Events
| Event | Detail | When |
|---|---|---|
gantt-chart:ready |
{ taskCount, dateRange } |
After the component finishes rendering |
gantt-chart:task-click |
{ task } |
When a task bar or milestone is clicked |
gantt-chart:upgraded |
— | Once after first connect; safe signal for the first .tasks assignment. |
gantt-chart:tasks-changed |
{ tasks, source: 'property' } |
After .tasks is assigned and the chart re-renders. |
Property API (JS-first)
For reactive frameworks, assign .tasks directly instead of templating the source <table>. Each task is a plain object; ISO date strings and timestamps both work.
| Property | Type | Description |
|---|---|---|
.tasks | Task[] | Setter replaces the chart contents and re-renders. Reading returns the normalized internal array. |
const g = document.querySelector('gantt-chart'); await new Promise(r => g.addEventListener('gantt-chart:upgraded', r, { once: true })); g.tasks = [ { id: 'T1', name: 'Design', start: '2026-04-01', end: '2026-04-15', progress: 0.6 }, { id: 'T2', name: 'Build', start: '2026-04-10', end: '2026-04-25', depends: ['T1'] }, { id: 'T3', name: 'Release', start: '2026-04-30', end: '2026-04-30', milestone: true },]; effect(() => { g.tasks = sprintTasks.get(); }); g.addEventListener('gantt-chart:tasks-changed', (e) => { if (e.detail.source !== 'property') syncStore(e.detail.tasks);});
See the Data API concepts guide for the dual-mode contract.
CSS Custom Properties
| Variable | Default | Description |
|---|---|---|
--gc-task-width | 180px | Left panel (task names) width |
--gc-row-height | 36px | Row height for tasks and bars |
--gc-bar-color | var(--color-interactive) | Per-bar color override |
Accessibility
- The original
<table>stays in the DOM (visually hidden) so screen readers get full table semantics with<th>,<time>, and<progress> - Each bar has
role="img"and a descriptivearia-label - Bars are focusable via
tabindex="0"for keyboard navigation - An
aria-live="polite"region announces task selection - All transitions respect
prefers-reduced-motion: reduce - Print styles show the original table and hide the visual chart
Related
<kanban-board>— Track tasks as draggable cards in columns<impact-effort>— Prioritize tasks on impact vs effort<user-story>— Story cards used as kanban items<review-surface>— Annotate the chart with review pins- UX Planning Pack — Loads all UX planning components together