Timeline

Incident Timeline

Status-page incident report with a Resolved badge, duration and affected services in the header, then four updates newest first, from Resolved back to Investigating, each with a time and note.

Free, MIT licenseOpen in Browse
import {
  Timeline,
  TimelineContent,
  TimelineDescription,
  TimelineIndicator,
  TimelineItem,
  TimelineLabel,
  TimelineTitle,
} from "@/components/editorial/timeline"
import { Badge } from "@/components/ui/badge"

// The copy: change text here, not in the markup.
const COPY = {
  eyebrow: "Incident",
  title: "Elevated error rates on the registry API",
  resolved: "Resolved",
  duration: "Duration 1h 48m",
  affected: "Affected: registry downloads, MCP search",
} as const

const UPDATES = [
  {
    status: "Resolved",
    time: "16:03 UTC",
    body: "Error rates have been normal for 30 minutes. The faulty node is out of rotation and will be rebuilt.",
  },
  {
    status: "Monitoring",
    time: "15:33 UTC",
    body: "A fix is deployed. We are watching error rates before closing.",
  },
  {
    status: "Identified",
    time: "14:51 UTC",
    body: "One cache node is returning stale certificates. Traffic is being drained from it.",
  },
  {
    status: "Investigating",
    time: "14:15 UTC",
    body: "We are looking into a rise in 5xx responses from the registry API in eu-west.",
  },
] as const

// The newest update leads and carries the ring; older ones are plain dots.
export default function IncidentTimeline() {
  return (
    <article className="mx-auto w-full max-w-xl">
      <header className="mb-8 border-b border-border pb-6">
        <p className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
          {COPY.eyebrow}
        </p>
        <h2 className="mt-2 text-xl font-semibold tracking-tight">
          {COPY.title}
        </h2>
        <div className="mt-3 flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
          <Badge>{COPY.resolved}</Badge>
          <span>{COPY.duration}</span>
          <span aria-hidden>·</span>
          <span>{COPY.affected}</span>
        </div>
      </header>
      <Timeline className="[--timeline-lh:1.25rem]">
        {UPDATES.map((u, i) => (
          <TimelineItem key={u.time}>
            <TimelineIndicator
              className={
                i > 0
                  ? "border-0 [&_[data-slot=timeline-dot]]:bg-muted-foreground/60"
                  : undefined
              }
            />
            <TimelineContent>
              <div className="flex flex-wrap items-baseline gap-x-3">
                <TimelineTitle className="text-sm/5">{u.status}</TimelineTitle>
                <TimelineLabel className="text-sm/5 tabular-nums">
                  {u.time}
                </TimelineLabel>
              </div>
              <TimelineDescription className="max-w-prose">
                {u.body}
              </TimelineDescription>
            </TimelineContent>
          </TimelineItem>
        ))}
      </Timeline>
    </article>
  )
}

Installation

pnpm dlx shadcn add @01orbital/incident-timeline
npx shadcn add @01orbital/incident-timeline
yarn shadcn add @01orbital/incident-timeline
bunx --bun shadcn add @01orbital/incident-timeline

Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.

It lands at components/blocks/timeline/incident-timeline/index.tsx, as source you own and edit.

Usage

import IncidentTimeline from "@/components/blocks/timeline/incident-timeline"

export default function Example() {
  return <IncidentTimeline />
}

Dependencies

Registry pieces it installs with it: