A vertical list of events, such as an activity feed, an order's shipping history or a ticket's audit trail. Each item has a dot, icon or avatar, a title, a time and optional content, joined by connector lines. Comes in default and compact densities, with tone-colored markers and an option to hide the connector.
<Timeline> {/* <ol>, variant, connector */} <TimelineItem> {/* <li> */} <TimelineIndicator /> {/* dot, icon tile or media + connector */} <TimelineContent> {/* fills the rest of the row */} <TimelineHeader> {/* title and time on one row */} <TimelineTitle /> <TimelineTime /> {/* <time> */} </TimelineHeader> <TimelineDescription /> {/* body text or any content */} </TimelineContent> </TimelineItem></Timeline>
Part
Renders
Notes
Timeline
<ol>
Sets --timeline-indicator (marker size) and --timeline-gap (space between items) for the chosen variant.
TimelineItem
<li>
A row: marker column plus content.
TimelineIndicator
<div aria-hidden>
With no children, a dot. With children, a 28px round icon tile (variant="icon") or a circle that an image or avatar fills (variant="media"). Draws the connector down to the next item.
TimelineContent
<div>
Aligns its first line with the marker and adds the gap below each item.
TimelineHeader
<div>
Flex row that wraps. The time is pushed to the end.
TimelineTitle
<div>
Foreground text. <strong> inside is medium weight, for names.
TimelineTime
<time>
Muted, tabular. Pass dateTime with an ISO value.
TimelineDescription
<div>
Muted body text. Can hold badges, quotes or cards.
Leave TimelineIndicator empty for a dot. Without color it's a neutral ring; with color it's filled with a soft halo, useful for the latest or most important event.
Order delivered
Left at the front door · Signed by R. Patel
Out for delivery
Van 14 · Brooklyn depot
Arrived at local depot
Brooklyn, NY
Shipped
Northfold warehouse, Newark, NJ
import { Timeline, TimelineContent, TimelineDescription, TimelineHeader, TimelineIndicator, TimelineItem, TimelineTime, TimelineTitle,} from "@/components/ui/timeline";const events = [ { title: "Order delivered", detail: "Left at the front door · Signed by R. Patel", time: "Today, 2:41 PM", iso: "2026-10-04T14:41", color: "success", }, { title: "Out for delivery", detail: "Van 14 · Brooklyn depot", time: "Today, 8:02 AM", iso: "2026-10-04T08:02", }, { title: "Arrived at local depot", detail: "Brooklyn, NY", time: "Oct 3, 9:47 PM", iso: "2026-10-03T21:47", }, { title: "Shipped", detail: "Northfold warehouse, Newark, NJ", time: "Oct 2, 4:15 PM", iso: "2026-10-02T16:15", },] as const;export default function TimelineDots() { return ( <Timeline className="w-full max-w-md" aria-label="Shipment history"> {events.map((e) => ( <TimelineItem key={e.iso}> <TimelineIndicator color={"color" in e ? e.color : undefined} /> <TimelineContent> <TimelineHeader> <TimelineTitle className="font-medium">{e.title}</TimelineTitle> <TimelineTime dateTime={e.iso}>{e.time}</TimelineTime> </TimelineHeader> <TimelineDescription>{e.detail}</TimelineDescription> </TimelineContent> </TimelineItem> ))} </Timeline> );}
The timeline is an <ol>, so screen readers announce the number of events. Add an aria-label when the context doesn't already make clear what the list is.
Markers are aria-hidden. If an icon or color carries meaning (e.g. failed vs. succeeded), say so in the title or description too.
TimelineTime renders a <time> element; pass a machine-readable dateTime alongside friendly text like "2h ago".
TimelineTitle is a <div>. If items are sections with their own headings, put a heading element inside it.