Handoff: Timeline (with TimelineItem)
What and why
A product often needs a log of events over time: deploys, checks, approvals, an agent joining and working. PDS has nothing for it, so teams draw the rail, the times and the status icons by hand. This proposal is two components: TimelineItem, one event or piece of work (an optional status icon, a label, quiet secondary text, a time, optional expandable detail and a slot for content under it), and Timeline, the list that owns list semantics, spacing and an optional rail joining the icons. TimelineItem is the former AgentStep, renamed and generalized (2026-10-16): an agent run is one region of a larger log, so the row is generic and the agent flavor is a boolean, isAgentic.
Dividers, a Chain of Thought or a Thinking State can sit between items. The full "agent joins an event log" composition is a recipe, Recipes/Event log with agent (proposals/EventLogWithAgent/), not a component.
- Figma / Jira: Figma page below under Preview; Jira none. Sources were designer screenshots (2026-10-12 and 2026-10-16): a deploy log with a "Zappy engaged" divider, an agent sandbox investigation, and a plain neutral log.
- Context:
context.md
Exploration and decisions
- Rename AgentStep to TimelineItem rather than keep a wrapper or build a second row. One row, with
isAgentic(the same boolean convention as Button, IconButton and MenuButton). Chain of Thought now renders aTimelinefor its body. - Timeline is a thin list. It earns its place because the rail must run through the gap between items, and something has to own the
ol/lisemantics. Each child becomes a list item. - Rail is optional (
hasRail, defaultfalse). It joins only neighbouringTimelineItems, so a divider, a Chain of Thought or a Thinking State between items breaks it. It is drawn by each entry's::after, inem, so it follows the item's text size. - The full event log is a recipe, not a component. Where the agent starts and stops, the divider wording and which entries are agentic are screen decisions.
isAgenticchanges the status icon color to the discovery foreground; errors stay critical.timesits at the end of the row; an expandable item with a time spans the full row and the chevron comes after the time.- Names:
Timeline/TimelineItemoverActivityLogandStep(Step implies a sequence). - Carried over from AgentStep (see
decisions.md): optionalstatus, configurableiconName, tool calls are items withdetail, achildrenslot under the row, Tag for file identifiers, detail text inherits the item's size.
API
Timeline
| Prop | Type | Default | Purpose |
|---|---|---|---|
children |
ReactNode |
none | What goes in the list. Each child becomes a list item. |
className |
string |
none | Additional class names. |
hasRail |
boolean |
false |
Draw a vertical rail joining neighbouring TimelineItems. Decorative. |
size |
'xs' | 's' |
'xs' |
Text size of the items, applied as a pds-copy-* class on the list. |
TimelineItem
| Prop | Type | Default | Purpose |
|---|---|---|---|
children |
ReactNode |
none | Content under the item, always visible. |
className |
string |
none | Additional class names. |
detail |
ReactNode |
none | Expandable content. An item with detail becomes a toggle. |
iconName |
IconProps['iconName'] |
none | Replaces the status icon. The active spinner still shows. |
isAgentic |
boolean |
false |
The agent's own item: the status icon takes the agent color. |
isQuiet |
boolean |
false |
Quiet text for secondary rows. |
label |
ReactNode |
required | What happened or is happening. |
labelStrings |
Record<TimelineItemStatus, string> |
In progress, Done, Failed, Pending | Translatable screen-reader text for the status. |
secondaryText |
ReactNode |
none | Quiet supporting text after the label. |
size |
'xs' | 's' |
none (inherits) | Text size, applied as a pds-copy-* class on the item. Unset inherits, for example from a Timeline's size. |
status |
'active' | 'done' | 'error' | 'pending' |
none | Where the item is at. Without it (and without iconName) there is no icon. |
time |
string |
none | When it happened, formatted by the consumer. Shown at the end of the row. |
timeDateTime |
string |
none | Machine-readable value for the time element. |
Variants and states: with and without rail; the four statuses; isAgentic on and off; with and without time, secondary text, detail and content; items with a custom icon.
Tokens used
--pds-animation-transition-rotation--pds-border-radius-default--pds-border-width-default--pds-border-width-outline--pds-color-border-separator--pds-color-foreground-default--pds-color-foreground-default-secondary--pds-color-interactive-focus--pds-color-status-critical-foreground--pds-color-status-discovery-foreground--pds-color-status-success-foreground--pds-spacing-2xs--pds-spacing-5xs--pds-spacing-l--pds-spacing-s--pds-spacing-xs--pds-typography-ff-code--pds-typography-ff-default--pds-typography-lh-m--pds-typography-size-xs
No raw values. Custom variables (see custom.md): --timeline-gap, --timeline-item-status-color, --timeline-rail-color.
Impact
From impact.md: nothing reaches existing PDS components; everything is scoped to pds-timeline and pds-timeline-item. Chain of Thought builds on it (it renders Timeline and its rows are TimelineItem), and Event log with agent composes it. Changing the rename's classes (pds-agent-step to pds-timeline-item, --agent-step-* to --timeline-item-*) reached Chain of Thought, which was updated. The local Spinner and Icon wrapper are used for the active spinner and icons.
Reused vs net-new
- Reused:
Icon(local wrapper),Spinner(local, for the active status); in stories, the package'sCodeBlock,TagandFlexContainer. - Net-new:
TimelineandTimelineItem(classespds-timeline*andpds-timeline-item*, four custom variables, no raw values). Seecustom.md.
Accessibility
Timelineis anol; each child is anli, so a screen reader announces the number of entries. A divider child keeps its ownseparatorrole.- The rail is a CSS pseudo-element, decorative, not announced.
- An item's status is announced as visually-hidden text (
labelStrings, translatable); the active spinner has its own label. timerenders atimeelement; passtimeDateTimefor a machine-readable value.- One chevron for every disclosure (2026-10-21): an item with
detailshows an angle-down that flips to up when open, the same as the Chain of Thought summary and the PDS Panel Row's expand button (it was a right chevron turning down). - An item with
detailis a nativedetails/summary: Enter and Space toggle it, and it takes the visible focus ring (--pds-color-interactive-focus). With a time, the whole row is the click target. - a11y panel: no violations on the Event log with agent story in light. Not run on the other stories, and not run in dark. Run it in both before the port.
Decisions and open questions
- Tone. Items take
isAgenticone by one. Should aTimelineGrouptint a region between two dividers? Only if the same region keeps being rebuilt. - Live tail.
ThinkingStateis a plain child today. ShouldTimelineItemget an active look instead? - Time column on narrow widths. The time is
flex-shrink: 0; a long label wraps. Confirm that is right. - Collapsing old entries ("Show N earlier events") is not built.
- Nesting depth. One level (a Chain of Thought inside the log) is tested; deeper is not.
- Custom icon on a done item replaces the check, so completion shows only by color and hidden text. Keep a small check?
- Content when an item finishes. The consumer decides whether to collapse it.
- Divider styling. Which agentic
LabeledDividerstyling stays (gradient or filled) is open on that proposal. - Not built: a larger, bolder item style, and a Chain of Thought header with a metadata line.
Port notes
- New components, likely
src/components/with atimelinefolder holding both. Usenpm run generate:componentfor each, then copy the files over. - Swap the local
Iconwrapper andmergeClassesfor the real ones, and port the Spinner proposal for the active status (ready:../Spinner/HANDOFF.md). The order is indocs/PORT-ORDER.md. - The rail uses the
lhCSS unit. Confirm it is within the browser support PDS targets. - Port Timeline and TimelineItem before Chain of Thought, which depends on both.
- AgentStep never shipped, so there is nothing to migrate in
pds-toolkit-react.
Preview
npm run storybook, then Component Proposals/Timeline/Timeline and Component Proposals/Timeline/Timeline Item. The composition is under Recipes/Event log with agent.
Figma: https://www.figma.com/design/AMGWJbraJoi2PKWnxo29Ah/PDS-Proposals?node-id=306-43, page Timeline (captured from Storybook, flat layers, light and dark).