All examples

Timeline and Gantt

Time-scaled Gantt with dependencies, zoom, critical path, RTL and keyboard rescheduling on a fictional launch plan.

Timeline and Gantt

data-timeline · data/timeline

data timeline
Props
  • dependencies
  • direction
  • now
  • tasks
  • zoom

Live example loads in an isolated same-origin preview frame.

Source (data-timeline/TimelineDemo.svelte)
<script lang="ts">
import { type Dependency, type TaskChange, TimelineView } from '@texuli/data/timeline';

interface DemoTask {
  id: string;
  title: string;
  start?: string;
  end?: string;
  milestone?: boolean;
  progress?: number;
  group?: string;
}

let tasks = $state<DemoTask[]>([
  {
    id: 'brief',
    title: 'Write product brief',
    start: '2026-02-02',
    end: '2026-02-06',
    progress: 100,
    group: 'Discovery',
  },
  {
    id: 'design',
    title: 'Design flows',
    start: '2026-02-09',
    end: '2026-02-13',
    progress: 70,
    group: 'Discovery',
  },
  {
    id: 'build',
    title: 'Build timeline view',
    start: '2026-02-16',
    end: '2026-02-27',
    progress: 35,
    group: 'Delivery',
  },
  {
    id: 'launch',
    title: 'Launch review',
    start: '2026-03-02',
    end: '2026-03-02',
    milestone: true,
    group: 'Delivery',
  },
  {
    id: 'parking',
    title: 'Parking-lot idea',
    group: 'Backlog',
  },
]);

const dependencies: Dependency[] = [
  { from: 'brief', to: 'design', type: 'FS' },
  { from: 'design', to: 'build', type: 'FS' },
  { from: 'build', to: 'launch', type: 'FS' },
];

let direction = $state<'ltr' | 'rtl'>('ltr');
let lastSave = $state('No changes yet.');

async function onReschedule(changes: readonly TaskChange[]): Promise<void> {
  const byId = new Map(changes.map((change) => [change.id, change]));
  tasks = tasks.map((task) => {
    const change = byId.get(task.id);
    if (!change?.start || !change.end) return task;
    return { ...task, start: change.start, end: change.end };
  });
  lastSave = `Saved ${changes.length} change${changes.length === 1 ? '' : 's'}.`;
}
</script>

<section data-example="data-timeline" aria-labelledby="timeline-demo-title">
  <header class="head">
    <div>
      <h2 id="timeline-demo-title">Timeline and Gantt</h2>
      <p>
        Fictional launch plan with dependencies, zoom, critical path and keyboard
        rescheduling. Drag a bar or focus it and press Space, then arrows.
      </p>
    </div>
    <label>
      Direction
      <select bind:value={direction} aria-label="Timeline direction">
        <option value="ltr">LTR</option>
        <option value="rtl">RTL</option>
      </select>
    </label>
  </header>
  <TimelineView
    {tasks}
    {dependencies}
    {direction}
    now={new Date('2026-02-18T12:00:00Z')}
    label="Northwind launch"
    getTitle={(task) => task.title}
    getProgress={(task) => task.progress}
    getGroup={(task) =>
      task.group ? { id: task.group.toLowerCase(), label: task.group } : undefined}
    onReschedule={onReschedule}
  />
  <p class="save" role="status">{lastSave}</p>
</section>

<style>
  section {
    display: grid;
    gap: 0.75rem;
    padding: 1rem;
    color: var(--tx-foreground, CanvasText);
    background: var(--tx-background, Canvas);
  }
  .head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 1rem;
    align-items: end;
  }
  h2 {
    margin: 0 0 0.25rem;
    font-size: 1.125rem;
  }
  p {
    margin: 0;
    color: var(--tx-muted-foreground, GrayText);
    max-inline-size: 40rem;
  }
  label {
    display: grid;
    gap: 0.25rem;
    font-size: 0.875rem;
  }
  select {
    border: 1px solid var(--tx-border, ButtonBorder);
    border-radius: 0.375rem;
    padding-block: 0.35rem;
    padding-inline: 0.5rem;
    background: var(--tx-surface, Canvas);
    color: inherit;
  }
  .save {
    font-size: 0.875rem;
  }
</style>