{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "timeline",
  "title": "Timeline",
  "description": "A vertical timeline: a list of events drawn as dots on a connecting line, each with an optional timestamp, title, description and icon. Reach for it wherever the content itself is \"what happened, and in what order\": an activity feed on a record, profile or dashboard; an audit, security or admin history log; a changelog or release-notes page; order, shipment and delivery tracking; a deploy, build or CI/CD run log; incident updates on a status page; a support ticket's history; approval and review trails; the event stream on an order, invoice or subscription; notification history; a product roadmap; and the experience or education list on a resume or \"about\" page. Common asks it answers: \"timeline component\", \"vertical timeline\", \"activity feed\", \"activity timeline\", \"history log component\", \"audit log UI\", \"changelog timeline\", \"release notes timeline\", \"order tracking timeline\", \"shipment tracking UI\", \"deploy history list\", \"event stream component\", \"status timeline\", \"shadcn timeline\", \"shadcn activity feed\", \"react vertical timeline alternative\", \"react-chrono alternative\", \"MUI Timeline equivalent\", \"antd Timeline equivalent\", \"resume timeline\". shadcn/ui ships no timeline: fetching the source of all 63 items in its registry and grepping them turns up no timeline, no <time> element and no connector line anywhere. The nearest things it has are marker — one annotation row of an icon beside muted text, with a variant that rules a line across it, the \"New messages\" divider in a chat — and item, a kit for laying out a single row; neither stacks events on a shared line nor carries a time. It is also not step-indicator, the other dots-on-a-line component here: a step indicator counts position through a sequence that is known in advance and still to be finished, while a timeline is the record of what already happened and has no current step. Pass an items array (title, optional time, description, icon and a colour accent). The timestamp renders as a semantic <time> element with a machine-readable dateTime, so it is legible to a reader and to a crawler; the connecting line and the decorative dots are aria-hidden, so a screen reader hears the events rather than the ornament; and each marker takes a per-item colour (muted, primary, success, warning, destructive), so status like succeeded, failed or pending can be flagged without reaching for a second component. Pass an icon to render an icon badge instead of a plain dot. It is a pure display component with no state, so it renders inside a server component with no \"use client\" and costs nothing on the client. Theme-aware via shadcn tokens with dark mode; no dependencies (bring your own icons).",
  "dependencies": [],
  "files": [
    {
      "path": "registry/ui/timeline.tsx",
      "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype TimelineColor =\n  | \"muted\"\n  | \"primary\"\n  | \"success\"\n  | \"warning\"\n  | \"destructive\"\n\n// Marker fill per accent. `muted`/`primary`/`destructive` ride shadcn tokens so\n// they follow the theme; `success`/`warning` use solid mid-tone status colors\n// (same convention as pulld's stat-card) since shadcn ships no such token.\nconst DOT_COLOR: Record<TimelineColor, string> = {\n  muted: \"bg-muted-foreground/40\",\n  primary: \"bg-primary\",\n  success: \"bg-emerald-500\",\n  warning: \"bg-amber-500\",\n  destructive: \"bg-destructive\",\n}\n\nexport interface TimelineItem {\n  /** Main heading for the event. */\n  title: React.ReactNode\n  /**\n   * Human-readable timestamp shown above the title, e.g. \"3m ago\" or\n   * \"Mar 4, 2026\".\n   */\n  time?: string\n  /** Machine-readable value for the rendered <time> element (e.g. an ISO date). */\n  dateTime?: string\n  /** Optional details rendered under the title. */\n  description?: React.ReactNode\n  /**\n   * Optional element (e.g. a lucide-react icon) rendered inside the marker\n   * instead of the default dot.\n   */\n  icon?: React.ReactNode\n  /** Accent color for the marker. Defaults to \"muted\". */\n  color?: TimelineColor\n}\n\ninterface TimelineProps extends React.ComponentPropsWithoutRef<\"ol\"> {\n  /** Events in the order they should appear, top to bottom. */\n  items: TimelineItem[]\n}\n\n/**\n * Vertical timeline: a list of events, each a marker on a connecting line with\n * an optional time, title, and description.\n */\nexport const Timeline = React.forwardRef<HTMLOListElement, TimelineProps>(\n  function Timeline({ items, className, ...props }, ref) {\n    const lastIndex = items.length - 1\n\n    return (\n      <ol ref={ref} className={cn(\"relative\", className)} {...props}>\n        {items.map((item, i) => {\n          const isLast = i === lastIndex\n          const color = item.color ?? \"muted\"\n\n          return (\n            <li key={i} className=\"relative flex gap-4 pb-8 last:pb-0\">\n              {/* Connector: centered on the marker and drawn behind it. Runs\n                  from this marker's center down to the next marker's center;\n                  omitted on the last item. The marker's solid fill masks it\n                  where they overlap. */}\n              {!isLast ? (\n                <span\n                  aria-hidden=\"true\"\n                  className=\"absolute left-3 top-3 h-full w-px -translate-x-1/2 bg-border\"\n                />\n              ) : null}\n\n              {/* Marker: an icon badge when an icon is given, otherwise a dot.\n                  The ring / bg-background halo separates it from the line. */}\n              <span className=\"relative z-10 flex h-6 w-6 shrink-0 items-center justify-center\">\n                {item.icon ? (\n                  <span\n                    aria-hidden=\"true\"\n                    className=\"flex h-6 w-6 items-center justify-center rounded-full border bg-background text-muted-foreground [&_svg]:h-3.5 [&_svg]:w-3.5\"\n                  >\n                    {item.icon}\n                  </span>\n                ) : (\n                  <span\n                    aria-hidden=\"true\"\n                    className={cn(\n                      \"h-3 w-3 rounded-full ring-4 ring-background\",\n                      DOT_COLOR[color]\n                    )}\n                  />\n                )}\n              </span>\n\n              {/* Content */}\n              <div className=\"flex-1 pt-0.5\">\n                {item.time ? (\n                  <time\n                    dateTime={item.dateTime}\n                    className=\"text-xs font-medium text-muted-foreground\"\n                  >\n                    {item.time}\n                  </time>\n                ) : null}\n                <p className=\"text-sm font-medium leading-tight text-foreground\">\n                  {item.title}\n                </p>\n                {item.description ? (\n                  <p className=\"mt-1 text-sm text-muted-foreground\">\n                    {item.description}\n                  </p>\n                ) : null}\n              </div>\n            </li>\n          )\n        })}\n      </ol>\n    )\n  }\n)\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}