Elements

56 / 122 · Structured output

Timeline

Events on a time axis, with what already happened and what is still coming.

09:02
Issue filedDraft survives a thread switch

Installation

npx shadcn@latest add "@assistant-ui/elements-timeline"

Usage

import { Timeline } from "@/components/elements/timeline";

<Timeline events={events} visibleCount={events.length} />

Props

Timeline

events*TimelineEvent[]Events in chronological order, past and scheduled together.
visibleCount*numberHow many events have been resolved.
classNamestringExtra classes merged onto the root.

TimelineEvent

id*stringStable identity for the row key.
title*stringWhat happened.
when*"past" | "now" | "future"past is filled, now gets a ring, future is an outline with a lighter rail.
time*stringPre-formatted timestamp in the left rail.
detailstringOptional second line under the title.

Source

timeline.tsx
"use client";

import { cn } from "@/lib/utils";
import { mono, paper } from "./surfaces";

export type TimelineWhen = "past" | "now" | "future";

export interface TimelineEvent {
  id: string;
  when: TimelineWhen;
  time: string;
  title: string;
  detail?: string;
}

export function Timeline({
  events,
  visibleCount,
  className,
}: {
  events: readonly TimelineEvent[];
  visibleCount: number;
  className?: string;
}) {
  return (
    <div
      className={cn(
        paper,
        "flex w-full max-w-sm flex-col rounded-2xl p-4",
        className,
      )}
    >
      {events.slice(0, visibleCount).map((event, i, shown) => (
        <div
          key={event.id}
          className="fade-in slide-in-from-left-1 animate-in fill-mode-both grid grid-cols-[3.5rem_1rem_1fr] gap-x-2 duration-300"
        >
          <span
            className={cn(
              mono,
              "pt-[3px] text-end tabular-nums",
              event.when === "future"
                ? "text-foreground/25"
                : "text-foreground/40",
            )}
          >
            {event.time}
          </span>

          <span className="flex flex-col items-center">
            <span
              className={cn(
                "mt-1 size-2 shrink-0 rounded-full",
                event.when === "now" &&
                  "bg-blue-500 ring-4 ring-blue-500/15 dark:bg-blue-400",
                event.when === "past" && "bg-foreground/30",
                event.when === "future" &&
                  "border-foreground/20 border bg-transparent",
              )}
            />
            {i < shown.length - 1 && (
              <span
                className={cn(
                  "w-px flex-1",
                  event.when === "future"
                    ? "bg-foreground/[0.08]"
                    : "bg-foreground/15",
                )}
              />
            )}
          </span>

          <div
            className={cn(
              "flex flex-col gap-0.5",
              i < shown.length - 1 && "pb-3",
            )}
          >
            <span
              className={cn(
                "text-[13px]",
                event.when === "future"
                  ? "text-foreground/40"
                  : "text-foreground/90",
                event.when === "now" && "font-medium",
              )}
            >
              {event.title}
            </span>
            {event.detail && (
              <span className="text-foreground/45 text-xs leading-relaxed">
                {event.detail}
              </span>
            )}
          </div>
        </div>
      ))}
    </div>
  );
}