- Order placedPaid by card, 1,840.00 USD.
- Packed
- Current stepOut for deliveryThe courier is 6 stops away.
- UpcomingDelivered
import { Package, ShoppingCart, Truck } from "lucide-react"
import {When to use it#
Use a timeline to show what happened, or what will happen, in order: the steps of an order, the activity on a ticket, the history of a customer, the stages of a document. The line between the events tells people that they belong together and which comes first.
A timeline only shows. It does not take part: your app gives each event its state and its content. For steps that people move through and answer, build the form with Tabs or a wizard, and use a horizontal timeline as the progress bar on top of it.
Import#
import {
Timeline,
TimelineItem,
TimelineMarker,
TimelineContent,
TimelineHeader,
TimelineTitle,
TimelineTime,
TimelineDescription,
} from "@hel/ui/timeline"Anatomy#
A Timeline is an ordered list of TimelineItems. Each item has a TimelineMarker, which is the circle on the line, and TimelineContent beside it. The content holds a TimelineHeader with a TimelineTitle and a TimelineTime, then a TimelineDescription, or the other parts below.
<Timeline aria-label="Order #10482">
<TimelineItem tone="success">
<TimelineMarker icon={ShoppingCart} />
<TimelineContent>
<TimelineHeader>
<TimelineTitle>Order placed</TimelineTitle>
<TimelineTime value={order.createdAt} />
</TimelineHeader>
<TimelineDescription>Paid by card.</TimelineDescription>
</TimelineContent>
</TimelineItem>
</Timeline>Give the timeline an aria-label, so it has a name. The line between events is drawn by the layout alone, from the state of the neighbours, so nothing is measured and you can add or remove items freely.
TimelineTime is a relative time such as "2 hours ago", and a date when you give it a format. Both are exact in the tooltip.
States#
state on an item says where it stands, and the marker, the lines and the text follow it.
| State | Meaning | Look |
|---|---|---|
done | It happened. This is the default. | A marker with its icon or a dot, joined by a solid line |
current | This is where things are now. | A ring around the marker |
upcoming | It has not happened yet. | A dashed, hollow marker, with a dashed line before it |
pending | It waits for someone or something. | A spinner |
failed | It went wrong. | An error icon, in the error color |
The state is also read to screen readers in words ("Current step", "Upcoming"), so it is not told by color and icon alone. A done event reads nothing extra by default. tone colors the marker: neutral, info, success, warning, error or primary.
Examples#
Activity#
A feed of what people did on a record. The marker can show a person with initials and src, and TimelineChanges lists the fields that changed, with the old value and the new one. Leave out from for a value that was set, and to for one that was cleared. TimelineDetails hides a longer text behind a button, and TimelineActions and TimelineMeta hold buttons and small print. An event that the system made, not a person, can be quiet, so it is muted, and a marker with no icon is a small dot.
- LFLena Fischer repliedI refunded the order. The money should be back in 3 to 5 days.
- OHOmar Haddad changed the ticketStatusOpenWaiting for refundPriorityNormalHighAssigneeLena Fischer
From the support queue
- The customer was emailed
- MTMaya Torres opened the ticketThe parcel arrived damaged.
import { Button } from "@hel/ui/button"
import {
Timeline,Layouts and states#
orientation="horizontal" makes a strip of steps, with the marker above the title. Use it for four or five short steps, because there is no room for more. In the default vertical timeline, align puts the content start or end of the line, or alternate on both sides in turn. size is sm, md or lg.
Horizontal, for a short strip of steps
- Cart
- Address
- Current stepPayment
- UpcomingConfirm
Pending, failed and upcoming events
- Uploaded
- PendingScanning for viruses
- FailedConverted to PDF
- UpcomingPublished
import {
Timeline,
TimelineContent,Groups, and loading more#
TimelineGroup puts events under a heading, such as a day or a month. The heading stays at the top while the events of its group scroll under it. Set surface="raised" on the timeline if it sits in a Card, so the heading matches the color behind it.
TimelineLoadMore is a button that belongs inside the timeline, after the last event. direction="newer" makes it for the start of the list, and loading shows a spinner on it while you fetch. While the very first events load, show a TimelineSkeleton in place of the whole timeline. A feed that updates while it is open can set live, so new events are announced politely.
Today
- Invoice INV-2044 was paid
- Customer Harbor Studio was added
Yesterday
- Invoice INV-2043 became overdue
- Report for September was exported
import * as React from "react"
import { Stack } from "@hel/ui/stack"
import {Props#
Timeline#
| Prop | Type | Default |
|---|---|---|
size | "sm" | "md" | "lg" | "md" |
orientation | "vertical" | "horizontal" | "vertical" |
align | "start" | "end" | "alternate" | "start" |
surface | "page" | "raised" | "page" |
live | boolean | false |
TimelineItem#
| Prop | Type | Default |
|---|---|---|
state | "done" | "current" | "upcoming" | "pending" | "failed" | "done" |
tone | "neutral" | "info" | "success" | "warning" | "error" | "primary" | "neutral", or "error" when failed |
quiet | boolean | false |
TimelineMarker#
| Prop | Type | Default |
|---|---|---|
icon | LucideIcon | none |
initials / src / alt | string | none |
children | ReactNode | none |
Other parts#
TimelineGroup takes heading. TimelineChanges takes changes, a list of { field, from, to }. TimelineDetails takes label, open and defaultOpen. TimelineSkeleton takes rows, and the props of a timeline. TimelineLoadMore takes direction, and the props of a Button.
The built-in texts, such as the state words read to screen readers and the labels of the buttons, can be changed with messages on the timeline, or translated for the whole app in the config.