Skip to content
Corsair UI
Browse the docs

ComponentsNo. 76 of 145

Timeline

A vertical list of events with a coloured dot for each status and a line joining them.

Installation

pnpm dlx shadcn@latest add @corsair-ui/timeline

No setup needed: the CLI finds @corsair-ui in the shadcn registry directory. The files land in your project as source: edit them like your own code. More in Installation.

Also adds
utils
npm
clsxtailwind-merge

Examples

Order history

The status colours the dot; the title says it too.

  1. Order placed

    Order #4821 for 3 items.

  2. Payment confirmed

  3. Delayed at the warehouse

    One item was restocked before packing.

  4. Shipped

    Handed to the carrier.

  5. Out for delivery

Deployments

Newest first.

  1. Deploy to production failed

    Build step exited with code 1. Rolled back automatically.

  2. Deploy to staging succeeded

    Version 2.4.0, 3 minutes.

  3. Deploy to staging started

API

PropTypeDefault
status

TimelineItem: colours the dot.

"default" | "success" | "warning" | "destructive""default"
dateTime

TimelineTime: the machine-readable moment; the visible text is yours.

string—

Styling

Restyle it in your project: className on each part, the theme variables, and these attributes with arbitrary variants, e.g. className="[&_[data-slot=timeline-connector]]:text-primary".

  • Each timeline-item has data-status ("default", "success", "warning", "destructive"); the dot reads it through group-data-[status=…]/timeline-item.
  • The dot uses --success, --warning and --destructive, with a ring in --background to cut the line.
data-slot
  • timeline
  • timeline-connector
  • timeline-content
  • timeline-description
  • timeline-dot
  • timeline-item
  • timeline-time
  • timeline-title
State
  • data-statuscomputed

Accessibility

  • An ordered list, so the number of events and each one's position are announced.
  • The dot and the line are hidden from screen readers: when the status matters, say it in the text.
  • TimelineTime is a time element with a machine-readable dateTime.

Compatibility

Tailwind CSS
3.4 and 4, both checked in CI
React
19
Rendering
Works in server components

Source

1 file, installed as source you own
timeline.tsxView on GitHub
import type { ComponentProps } from "react";
import { cn } from "@/registry/default/lib/utils";

type TimelineStatus = "default" | "success" | "warning" | "destructive";

/**
 * A vertical list of events in order: a ship's log, an order's history, a
 * deploy log. It is an ordered list (`<ol>`), so screen readers announce how
 * many events there are and where each one sits. Each `TimelineItem` has a
 * `status` that colours its dot; the dot and the line joining the dots are
 * decorative, so when the status matters, say it in the title or the
 * description too. Dates go in `TimelineTime`, a `<time>` with a
 * machine-readable `dateTime`; format the visible text for your locale.
 * Nothing animates.
 *
 * @example
 * <Timeline>
 *   <TimelineItem status="success">
 *     <TimelineDot />
 *     <TimelineConnector />
 *     <TimelineContent>
 *       <TimelineTitle>Left port</TimelineTitle>
 *       <TimelineTime dateTime="2025-03-02T08:00">2 March, 08:00</TimelineTime>
 *       <TimelineDescription>Cleared the harbour with a fair wind.</TimelineDescription>
 *     </TimelineContent>
 *   </TimelineItem>
 *   <TimelineItem>
 *     <TimelineDot />
 *     <TimelineConnector />
 *     <TimelineContent>
 *       <TimelineTitle>At sea</TimelineTitle>
 *     </TimelineContent>
 *   </TimelineItem>
 * </Timeline>
 */
function Timeline({ className, ...props }: ComponentProps<"ol">) {
  return <ol data-slot="timeline" className={cn("flex flex-col", className)} {...props} />;
}

interface TimelineItemProps extends ComponentProps<"li"> {
  /** Colours the dot, and is exposed as `data-status` for your own styles. */
  status?: TimelineStatus;
}

/** One event. Holds a `TimelineDot`, a `TimelineConnector` and a `TimelineContent`. */
function TimelineItem({ className, status = "default", ...props }: TimelineItemProps) {
  return (
    <li
      data-slot="timeline-item"
      data-status={status}
      className={cn("group/timeline-item relative flex gap-3 pb-6 last:pb-0", className)}
      {...props}
    />
  );
}

/** The marker of an event, coloured by the item's `status`. Decorative: hidden from screen readers. */
function TimelineDot({ className, ...props }: ComponentProps<"span">) {
  return (
    <span
      data-slot="timeline-dot"
      aria-hidden="true"
      className={cn(
        "bg-primary ring-background relative z-[1] mt-1.5 size-3 shrink-0 rounded-full ring-4",
        "group-data-[status=success]/timeline-item:bg-success",
        "group-data-[status=warning]/timeline-item:bg-warning",
        "group-data-[status=destructive]/timeline-item:bg-destructive",
        className
      )}
      {...props}
    />
  );
}

/**
 * The line from this event's dot down to the next one. Decorative: hidden
 * from screen readers, and not drawn on the last item.
 */
function TimelineConnector({ className, ...props }: ComponentProps<"span">) {
  return (
    <span
      data-slot="timeline-connector"
      aria-hidden="true"
      className={cn(
        "bg-border absolute top-6 bottom-0 left-[calc(0.375rem-0.5px)] w-px group-last/timeline-item:hidden",
        className
      )}
      {...props}
    />
  );
}

function TimelineContent({ className, ...props }: ComponentProps<"div">) {
  return (
    <div
      data-slot="timeline-content"
      className={cn("flex min-w-0 flex-1 flex-col gap-1", className)}
      {...props}
    />
  );
}

function TimelineTitle({ className, ...props }: ComponentProps<"p">) {
  return (
    <p
      data-slot="timeline-title"
      className={cn("text-sm leading-6 font-medium", className)}
      {...props}
    />
  );
}

interface TimelineTimeProps extends ComponentProps<"time"> {
  /** The moment in a machine-readable format (`2025-03-02`, `2025-03-02T08:00Z`). */
  dateTime: string;
}

/** When the event happened: a `<time>`; its text is yours to format for the reader's locale. */
function TimelineTime({ className, ...props }: TimelineTimeProps) {
  return (
    <time
      data-slot="timeline-time"
      className={cn("text-muted-foreground text-xs", className)}
      {...props}
    />
  );
}

function TimelineDescription({ className, ...props }: ComponentProps<"p">) {
  return (
    <p
      data-slot="timeline-description"
      className={cn("text-muted-foreground text-sm", className)}
      {...props}
    />
  );
}

export {
  Timeline,
  TimelineConnector,
  TimelineContent,
  TimelineDescription,
  TimelineDot,
  TimelineItem,
  TimelineTime,
  TimelineTitle,
  type TimelineItemProps,
  type TimelineStatus,
  type TimelineTimeProps,
};