<bpdm/ui />
Data

Status Timeline

A vertical status timeline for lifecycles — complete / current / pending / failed steps with timestamps, in React (@bpdm/ui) and Angular (@bpdm/ng).

The Status Timeline shows a lifecycle — a deployment, an approval, an onboarding flow — vertically (default) or horizontally. Each step carries a status (complete ✓, current — pulsing, pending — hollow, failed ✗), with an optional timestamp and description. It's data-driven: pass an items array.

Usage

Pass items — each with a title and a status. Add a timestamp (shown on the right) and a description where useful.

  1. Completed

    Pushed to main

    09:41
  2. Completed

    CI checks passed

    09:46
  3. In progress

    Deploying to staging

    09:47
  4. Not started

    Promote to production

deploy-timeline.tsx
import { StatusTimeline, type TimelineItem } from '@bpdm/ui/status-timeline';

const steps: TimelineItem[] = [
  { title: 'Pushed to main', status: 'complete', timestamp: '09:41' },
  { title: 'CI checks passed', status: 'complete', timestamp: '09:46' },
  { title: 'Deploying to staging', status: 'current', timestamp: '09:47' },
  { title: 'Promote to production', status: 'pending' },
];

export function DeployTimeline() {
  return <StatusTimeline items={steps} />;
}
deploy-timeline.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type TimelineItem } from '@bpdm/ng';

@Component({
  selector: 'deploy-timeline',
  imports: [BpdmStatusTimeline],
  template: `<bpdm-status-timeline [items]="steps" />`,
})
export class DeployTimeline {
  readonly steps: TimelineItem[] = [
    { title: 'Pushed to main', status: 'complete', timestamp: '09:41' },
    { title: 'CI checks passed', status: 'complete', timestamp: '09:46' },
    { title: 'Deploying to staging', status: 'current', timestamp: '09:47' },
    { title: 'Promote to production', status: 'pending' },
  ];
}

Statuses

The four status values — complete, current, pending, failed — each get a distinct marker. The current marker pulses; failed is destructive-red.

  1. Completed

    Complete

    done

    A finished step.

  2. In progress

    Current

    now

    In progress (the marker pulses).

  3. Not started

    Pending

    Not started yet.

  4. Failed

    Failed

    error

    Something went wrong.

statuses.tsx
import { StatusTimeline, type TimelineItem } from '@bpdm/ui/status-timeline';

const items: TimelineItem[] = [
  { title: 'Complete', status: 'complete', timestamp: 'done', description: 'A finished step.' },
  { title: 'Current', status: 'current', timestamp: 'now', description: 'In progress (the marker pulses).' },
  { title: 'Pending', status: 'pending', description: 'Not started yet.' },
  { title: 'Failed', status: 'failed', timestamp: 'error', description: 'Something went wrong.' },
];

export function Statuses() {
  return <StatusTimeline items={items} />;
}
statuses.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type TimelineItem } from '@bpdm/ng';

@Component({
  selector: 'statuses',
  imports: [BpdmStatusTimeline],
  template: `<bpdm-status-timeline [items]="items" />`,
})
export class Statuses {
  readonly items: TimelineItem[] = [
    { title: 'Complete', status: 'complete', timestamp: 'done', description: 'A finished step.' },
    { title: 'Current', status: 'current', timestamp: 'now', description: 'In progress (the marker pulses).' },
    { title: 'Pending', status: 'pending', description: 'Not started yet.' },
    { title: 'Failed', status: 'failed', timestamp: 'error', description: 'Something went wrong.' },
  ];
}

With a failure

A failed step turns red and carries its reason in description — the steps after it stay pending.

  1. Completed

    Build

    11:02
  2. Completed

    Unit tests

    11:05
  3. Failed

    E2E tests

    11:09

    2 specs failed — pipeline stopped.

  4. Not started

    Deploy

failed-pipeline.tsx
import { StatusTimeline, type TimelineItem } from '@bpdm/ui/status-timeline';

const steps: TimelineItem[] = [
  { title: 'Build', status: 'complete', timestamp: '11:02' },
  { title: 'Unit tests', status: 'complete', timestamp: '11:05' },
  { title: 'E2E tests', status: 'failed', timestamp: '11:09', description: '2 specs failed — pipeline stopped.' },
  { title: 'Deploy', status: 'pending' },
];

export function FailedPipeline() {
  return <StatusTimeline items={steps} />;
}
failed-pipeline.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type TimelineItem } from '@bpdm/ng';

@Component({
  selector: 'failed-pipeline',
  imports: [BpdmStatusTimeline],
  template: `<bpdm-status-timeline [items]="steps" />`,
})
export class FailedPipeline {
  readonly steps: TimelineItem[] = [
    { title: 'Build', status: 'complete', timestamp: '11:02' },
    { title: 'Unit tests', status: 'complete', timestamp: '11:05' },
    { title: 'E2E tests', status: 'failed', timestamp: '11:09', description: '2 specs failed — pipeline stopped.' },
    { title: 'Deploy', status: 'pending' },
  ];
}

Alignment

align sets which side the content sits on relative to the line — left (default), right, or alternate (zig-zag). The demo switches between them with our own Tabs.

  1. Completed

    Pushed to main

    09:41
  2. Completed

    CI checks passed

    09:46
  3. In progress

    Deploying to staging

    09:47
  4. Not started

    Promote to production

alignment.tsx
import { StatusTimeline, type TimelineItem } from '@bpdm/ui/status-timeline';

const steps: TimelineItem[] = [
  { title: 'Pushed to main', status: 'complete', timestamp: '09:41' },
  { title: 'CI checks passed', status: 'complete', timestamp: '09:46' },
  { title: 'Deploying to staging', status: 'current', timestamp: '09:47' },
  { title: 'Promote to production', status: 'pending' },
];

export function Alignment() {
  return <StatusTimeline items={steps} align="alternate" />; // or "left" | "right"
}
alignment.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type TimelineItem } from '@bpdm/ng';

@Component({
  selector: 'alignment',
  imports: [BpdmStatusTimeline],
  template: `<bpdm-status-timeline [items]="steps" align="alternate" />`, // or "left" | "right"
})
export class Alignment {
  readonly steps: TimelineItem[] = [
    { title: 'Pushed to main', status: 'complete', timestamp: '09:41' },
    { title: 'CI checks passed', status: 'complete', timestamp: '09:46' },
    { title: 'Deploying to staging', status: 'current', timestamp: '09:47' },
    { title: 'Promote to production', status: 'pending' },
  ];
}

Dual-sided

Add opposite to an item to show content on the other side of the line — a date, a duration, an actor. Any item having opposite centers the line so both sides read.

  1. Completed

    Submitted

    15 Oct, 10:30
  2. Completed

    Under review

    15 Oct, 14:00
  3. In progress

    Approved

    16 Oct, 09:15
  4. Not started

    Published

    16 Oct, 11:00
opposite.tsx
import { StatusTimeline, type TimelineItem } from '@bpdm/ui/status-timeline';

const steps: TimelineItem[] = [
  { title: 'Submitted', status: 'complete', opposite: '15 Oct, 10:30' },
  { title: 'Under review', status: 'complete', opposite: '15 Oct, 14:00' },
  { title: 'Approved', status: 'current', opposite: '16 Oct, 09:15' },
  { title: 'Published', status: 'pending', opposite: '16 Oct, 11:00' },
];

export function DualSided() {
  return <StatusTimeline items={steps} />;
}
opposite.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type TimelineItem } from '@bpdm/ng';

@Component({
  selector: 'dual-sided',
  imports: [BpdmStatusTimeline],
  template: `<bpdm-status-timeline [items]="steps" />`,
})
export class DualSided {
  readonly steps: TimelineItem[] = [
    { title: 'Submitted', status: 'complete', opposite: '15 Oct, 10:30' },
    { title: 'Under review', status: 'complete', opposite: '15 Oct, 14:00' },
    { title: 'Approved', status: 'current', opposite: '16 Oct, 09:15' },
    { title: 'Published', status: 'pending', opposite: '16 Oct, 11:00' },
  ];
}

Horizontal

Set layout="horizontal" to run the timeline left-to-right. In this orientation align is top (default), bottom, or alternate. The row scrolls horizontally when it overflows.

  1. Completed

    Discovery

    Q1

  2. Completed

    Design

    Q2

  3. In progress

    Build

    Q3

  4. Not started

    Launch

    Q4

roadmap.tsx
import { StatusTimeline, type TimelineItem } from '@bpdm/ui/status-timeline';

const roadmap: TimelineItem[] = [
  { id: 'discovery', title: 'Discovery', status: 'complete', timestamp: 'Q1' },
  { id: 'design', title: 'Design', status: 'complete', timestamp: 'Q2' },
  { id: 'build', title: 'Build', status: 'current', timestamp: 'Q3' },
  { id: 'launch', title: 'Launch', status: 'pending', timestamp: 'Q4' },
];

export function Roadmap() {
  return <StatusTimeline layout="horizontal" align="top" items={roadmap} />; // or "bottom" | "alternate"
}
roadmap.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type TimelineItem } from '@bpdm/ng';

@Component({
  selector: 'roadmap',
  imports: [BpdmStatusTimeline],
  template: `<bpdm-status-timeline layout="horizontal" align="top" [items]="roadmap" />`, // or "bottom" | "alternate"
})
export class Roadmap {
  readonly roadmap: TimelineItem[] = [
    { id: 'discovery', title: 'Discovery', status: 'complete', timestamp: 'Q1' },
    { id: 'design', title: 'Design', status: 'complete', timestamp: 'Q2' },
    { id: 'build', title: 'Build', status: 'current', timestamp: 'Q3' },
    { id: 'launch', title: 'Launch', status: 'pending', timestamp: 'Q4' },
  ];
}

Icons, colours & interactivity

The quickest way to customise — no render function needed. Give an item a custom icon (React) and/or color to override the default status glyph, and pass onItemClick to make each step interactive (keyboard-accessible; in Angular set interactive and listen to (itemClick)). For a fully custom marker or a rich content card, see Total control below.

  1. Not started

    Planned

    Q1
  2. In progress

    In development

    now
  3. Not started

    Beta launched

    Q2
  4. Not started

    General availability

    soon
releases.tsx
import { StatusTimeline, type TimelineItem } from '@bpdm/ui/status-timeline';
import { Flag, Rocket } from 'lucide-react';

const releases: TimelineItem[] = [
  { id: 'plan', title: 'Planned', color: '#8b5cf6', icon: <Flag className="size-3.5" />, timestamp: 'Q1' },
  { id: 'build', title: 'In development', status: 'current', timestamp: 'now' },
  { id: 'beta', title: 'Beta launched', color: '#0ea5e9', icon: <Rocket className="size-3.5" />, timestamp: 'Q2' },
  { id: 'ga', title: 'General availability', status: 'pending', timestamp: 'soon' },
];

export function Releases() {
  return <StatusTimeline items={releases} onItemClick={(item) => console.log(item.id)} />;
}
releases.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type TimelineItem } from '@bpdm/ng';

@Component({
  selector: 'releases',
  imports: [BpdmStatusTimeline],
  template: `
    <bpdm-status-timeline
      [items]="releases"
      [markerTemplate]="marker"
      interactive
      (itemClick)="onClick($event)"
    />
    <ng-template #marker let-item let-status="status">
      <!-- render a custom glyph per item / status -->
    </ng-template>
  `,
})
export class Releases {
  readonly releases: TimelineItem[] = [
    { id: 'plan', title: 'Planned', color: '#8b5cf6', timestamp: 'Q1' },
    { id: 'build', title: 'In development', status: 'current', timestamp: 'now' },
    { id: 'beta', title: 'Beta launched', color: '#0ea5e9', timestamp: 'Q2' },
    { id: 'ga', title: 'General availability', status: 'pending', timestamp: 'soon' },
  ];
  onClick(e: { item: TimelineItem; index: number }) {
    console.log(e.item.id);
  }
}

Total control

Hand the component a shell and render each slot yourself — the line, layout, alignment, and interactivity stay handled. React uses render props (renderMarker / renderContent / renderOpposite); Angular uses markerTemplate / contentTemplate / oppositeTemplate (context: item + index + status). Provide only the slots you need — the rest fall back to the defaults. Reach for these only when you need a fully custom marker or card; for a simple per-item icon or colour, prefer icon / color above.

  1. Completed
    JDRepository created

    Initialised the monorepo and CI pipeline.

    • README + license
    • CI workflow
    • Lint & format config

    Oct 15

    10:30

  2. Completed
    SYDeployed to staging

    First build shipped to the staging environment.

    Oct 15

    10:32

  3. In progress
    MKQA reviewIn progress

    Running the acceptance checklist before release.

    Oct 16

    14:15

  4. Not started
    JDProduction release

    Awaiting final sign-off from the release owner.

    Oct 18

    11:20

activity.tsx
import { StatusTimeline, type TimelineItem } from '@bpdm/ui/status-timeline';

type Event = TimelineItem & { user: string; date: string; color: string };

const events: Event[] = [
  { id: 'repo', user: 'JD', title: 'Repository created', status: 'complete', date: 'Oct 15', color: '#3b82f6' },
  { id: 'qa', user: 'MK', title: 'QA review', status: 'current', date: 'Oct 16', color: '#f97316' },
  { id: 'ga', user: 'JD', title: 'Production release', status: 'pending', date: 'Oct 18', color: '#84cc16' },
];

export function Activity() {
  return (
    <StatusTimeline
      items={events}
      align="alternate"
      renderOpposite={(e) => <span className="text-fd-muted-foreground">{(e as Event).date}</span>}
      renderMarker={(e) => (
        <span className="flex size-11 items-center justify-center rounded-full text-white shadow-lg"
          style={{ backgroundColor: (e as Event).color }} />
      )}
      renderContent={(e) => (
        <div className="rounded-xl border p-4 text-left shadow-sm">
          <p className="font-semibold">{e.title}</p>
        </div>
      )}
    />
  );
}
activity.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type TimelineItem } from '@bpdm/ng';

type Event = TimelineItem & { user: string; date: string; color: string };

@Component({
  selector: 'activity',
  imports: [BpdmStatusTimeline],
  template: `
    <bpdm-status-timeline
      [items]="events"
      align="alternate"
      [markerTemplate]="marker"
      [contentTemplate]="content"
      [oppositeTemplate]="opposite"
    >
      <ng-template #opposite let-e>
        <span class="text-muted-foreground">{{ e.date }}</span>
      </ng-template>
      <ng-template #marker let-e>
        <span class="flex size-11 items-center justify-center rounded-full text-white shadow-lg"
          [style.background-color]="e.color"></span>
      </ng-template>
      <ng-template #content let-e>
        <div class="rounded-xl border p-4 text-left shadow-sm">
          <p class="font-semibold">{{ e.title }}</p>
        </div>
      </ng-template>
    </bpdm-status-timeline>
  `,
})
export class Activity {
  readonly events: Event[] = [
    { id: 'repo', user: 'JD', title: 'Repository created', status: 'complete', date: 'Oct 15', color: '#3b82f6' },
    { id: 'qa', user: 'MK', title: 'QA review', status: 'current', date: 'Oct 16', color: '#f97316' },
    { id: 'ga', user: 'JD', title: 'Production release', status: 'pending', date: 'Oct 18', color: '#84cc16' },
  ];
}

Guided workflow

Because every slot is yours, the timeline composes into full step-based workflows. Here an onboarding flow tracks state, completes the current step when its marker is clicked, and shows progress with our own Progress Bar — all in your own component; the timeline just draws the line and lays it out.

Onboarding progress

1 of 5 steps completed

  1. Completed

    Account created

  2. In progress

    Email verified

    Click the marker to complete

  3. Not started3

    Profile completed

  4. Not started4

    Team invited

  5. Not started5

    First project created

onboarding.tsx
import { useState } from 'react';
import { StatusTimeline, type TimelineItem } from '@bpdm/ui/status-timeline';
import { ProgressBar } from '@bpdm/ui/progress';
import { Button } from '@bpdm/ui/button';

const STEPS = [
  { id: 1, label: 'Account created' },
  { id: 2, label: 'Email verified' },
  { id: 3, label: 'Profile completed' },
  { id: 4, label: 'Team invited' },
  { id: 5, label: 'First project created' },
];

export function Onboarding() {
  const [completed, setCompleted] = useState<number[]>([1]);
  const [current, setCurrent] = useState(2);
  const total = STEPS.length;
  const statusOf = (id: number) =>
    completed.includes(id) ? 'completed' : id === current ? 'current' : 'pending';
  const complete = (id: number) => {
    if (id !== current) return;
    setCompleted((c) => [...c, id]);
    setCurrent((c) => c + 1);
  };

  const items: TimelineItem[] = STEPS.map((s) => ({
    id: s.id,
    status: statusOf(s.id) === 'completed' ? 'complete' : statusOf(s.id) === 'current' ? 'current' : 'pending',
  }));

  return (
    <div>
      <div className="flex items-center justify-between">
        <p className="text-sm text-muted-foreground">{completed.length} of {total} completed</p>
        <Button variant="secondary" appearance="outline" size="sm"
          onClick={() => { setCompleted([1]); setCurrent(2); }}>Reset</Button>
      </div>
      <ProgressBar className="my-4" value={completed.length} max={total} variant="success" />

      <StatusTimeline
        items={items}
        renderMarker={(item) => {
          const id = item.id as number;
          if (statusOf(id) === 'current')
            return (
              <button onClick={() => complete(id)} aria-label={`Complete step ${id}`}
                className="grid size-7 cursor-pointer place-items-center rounded-full bg-primary text-primary-foreground ring-4 ring-primary/20 transition-transform hover:scale-110">
                {id}
              </button>
            );
          return <span className="grid size-7 place-items-center rounded-full bg-muted text-muted-foreground">{id}</span>;
        }}
        renderContent={(item) => {
          const step = STEPS.find((s) => s.id === item.id)!;
          return <p className="px-3 py-2 text-sm font-medium">{step.label}</p>;
        }}
      />
    </div>
  );
}
onboarding.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type TimelineItem } from '@bpdm/ng';
import { BpdmProgressBar } from '@bpdm/ng';
import { BpdmButton } from '@bpdm/ng';

interface Step { id: number; label: string; }

@Component({
  selector: 'onboarding',
  imports: [BpdmStatusTimeline, BpdmProgressBar, BpdmButton],
  template: `
    <div class="flex items-center justify-between">
      <p class="text-sm text-muted-foreground">{{ completed.length }} of {{ steps.length }} completed</p>
      <bpdm-button variant="secondary" appearance="outline" size="sm" (click)="reset()">Reset</bpdm-button>
    </div>
    <bpdm-progress-bar class="my-4 block" [value]="completed.length" [max]="steps.length" variant="success" />

    <bpdm-status-timeline [items]="items()" [markerTemplate]="marker" [contentTemplate]="content">
      <ng-template #marker let-item>
        @if (statusOf(item.id) === 'current') {
          <button (click)="complete(item.id)" [attr.aria-label]="'Complete step ' + item.id"
            class="grid size-7 cursor-pointer place-items-center rounded-full bg-primary text-primary-foreground ring-4 ring-primary/20 transition-transform hover:scale-110">
            {{ item.id }}
          </button>
        } @else {
          <span class="grid size-7 place-items-center rounded-full bg-muted text-muted-foreground">{{ item.id }}</span>
        }
      </ng-template>
      <ng-template #content let-item>
        <p class="px-3 py-2 text-sm font-medium">{{ label(item.id) }}</p>
      </ng-template>
    </bpdm-status-timeline>
  `,
})
export class Onboarding {
  readonly steps: Step[] = [
    { id: 1, label: 'Account created' },
    { id: 2, label: 'Email verified' },
    { id: 3, label: 'Profile completed' },
    { id: 4, label: 'Team invited' },
    { id: 5, label: 'First project created' },
  ];
  completed = [1];
  current = 2;

  statusOf = (id: number) =>
    this.completed.includes(id) ? 'completed' : id === this.current ? 'current' : 'pending';
  label = (id: number) => this.steps.find((s) => s.id === id)!.label;
  items = () =>
    this.steps.map((s): TimelineItem => ({
      id: s.id,
      status: this.statusOf(s.id) === 'completed' ? 'complete' : this.statusOf(s.id) === 'current' ? 'current' : 'pending',
    }));

  complete(id: number) {
    if (id !== this.current) return;
    this.completed = [...this.completed, id];
    this.current++;
  }
  reset() {
    this.completed = [1];
    this.current = 2;
  }
}

Internationalization

Every step renders a visually-hidden status label so screen-reader users hear its state (see Accessibility). Those four strings — and only those — are translatable via messages, a Partial<StatusTimelineMessages> merged over the English defaults, so you localize just the subset you need. Everything visible (title, timestamp, description, opposite) is your own content, translated at the call site.

The layout uses logical CSS properties throughout, so the timeline mirrors automatically under dir="rtl" — no prop needed.

status-timeline-i18n.tsx
import { StatusTimeline, type StatusTimelineMessages, type TimelineItem } from '@bpdm/ui/status-timeline';

// German overrides — pass any subset; the rest fall back to English
const de: Partial<StatusTimelineMessages> = {
  complete: 'Abgeschlossen',
  current: 'In Bearbeitung',
  pending: 'Ausstehend',
  failed: 'Fehlgeschlagen',
};

const steps: TimelineItem[] = [
  { title: 'Nach main gepusht', status: 'complete', timestamp: '09:41' },
  { title: 'CI-Prüfungen bestanden', status: 'complete', timestamp: '09:46' },
  { title: 'Wird auf Staging bereitgestellt', status: 'current', timestamp: '09:47' },
  { title: 'Für Produktion freigeben', status: 'pending' },
];

export function DeployTimeline() {
  return <StatusTimeline items={steps} label="Bereitstellungsverlauf" messages={de} />;
}
status-timeline-i18n.ts
import { Component } from '@angular/core';
import { BpdmStatusTimeline, type StatusTimelineMessages, type TimelineItem } from '@bpdm/ng';

@Component({
  selector: 'deploy-timeline',
  imports: [BpdmStatusTimeline],
  template: `
    <bpdm-status-timeline [items]="steps" label="Bereitstellungsverlauf" [messages]="de" />
  `,
})
export class DeployTimeline {
  // German overrides — pass any subset; the rest fall back to English
  readonly de: Partial<StatusTimelineMessages> = {
    complete: 'Abgeschlossen',
    current: 'In Bearbeitung',
    pending: 'Ausstehend',
    failed: 'Fehlgeschlagen',
  };

  readonly steps: TimelineItem[] = [
    { title: 'Nach main gepusht', status: 'complete', timestamp: '09:41' },
    { title: 'CI-Prüfungen bestanden', status: 'complete', timestamp: '09:46' },
    { title: 'Wird auf Staging bereitgestellt', status: 'current', timestamp: '09:47' },
    { title: 'Für Produktion freigeben', status: 'pending' },
  ];
}

API

StatusTimeline / <bpdm-status-timeline>

PropTypeDefaultDescription
itemsTimelineItem[]Required. The ordered steps.
labelstringAccessible name for the timeline (sets aria-label on the list).
layoutvertical | horizontalverticalOrientation of the timeline.
alignvertical: left | right | alternate · horizontal: top | bottom | alternateleft / topWhich side the content sits on.
onItemClick (Angular interactive + itemClick)(item, index) => voidMake steps interactive (keyboard-accessible). Angular: set interactive and listen to (itemClick).
renderMarker (Angular markerTemplate)(item, status) => ReactNode / TemplateRefRender the whole marker yourself (any size/shape).
renderContent (Angular contentTemplate)(item, index) => ReactNode / TemplateRefRender the whole content cell yourself (a rich card, etc.).
renderOpposite (Angular oppositeTemplate)(item, index) => ReactNode / TemplateRefRender the whole opposite cell yourself (vertical layouts).
messagesPartial<StatusTimelineMessages>Override the visually-hidden status labels announced to screen readers (i18n).
classNamestring (Angular class)Extra classes on the timeline.

StatusTimelineMessages

The visually-hidden status text announced for each step (screen readers), merged over the English defaults — override any subset.

KeyDefault
complete"Completed"
current"In progress"
pending"Not started"
failed"Failed"

TimelineItem

FieldTypeDescription
idstring | numberStable key for change tracking (falls back to index).
titleReactNode (Angular string)The step label.
statuscomplete | current | pending | failedThe step's state (drives the marker + colour).
timestampstringShort meta shown on the right, e.g. "09:47".
descriptionReactNode (Angular string)Optional detail under the title.
oppositeReactNode (Angular string)Content shown across the line (e.g. a date); centers the line when present.
icon (React)ReactNodeCustom marker glyph, overriding the default ✓/✗/dot.
colorstringCustom marker colour (any CSS colour / token), overriding the status colour.

Accessibility

  • Renders an ordered list (<ol> / <li>) — steps are announced in sequence. Pass label to name the list, and the current step carries aria-current="step".
  • Status is never conveyed by colour or glyph alone: the marker (✓ complete, a filled pulsing dot for current, a hollow ring for pending, ✗ failed) is decorative (aria-hidden), so each step also renders a visually-hidden status label — "Completed" / "In progress" / "Not started" / "Failed" — that screen-reader users hear alongside the title. Those four strings are translatable via messages (see Internationalization).
  • The current step's pulse respects reduced-motion preferences.
  • timestamp and description are plain text, read alongside the step title.
  • Interactive steps (onItemClick / interactive) expose role="button", are focusable, and respond to Enter / Space — fully keyboard-operable.
  • Layout uses logical CSS properties, so the timeline mirrors correctly under dir="rtl" with no extra configuration.

On this page