More Dashboards Snippets
AI Agent Steps Timeline — HTML CSS JS Snippet
AI Agent Steps Timeline · Dashboards · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
AI Agent Steps Timeline — Tool-Call Trace, Running/Done States, Expandable Payloads & Elapsed Clock

Agentic AI products — coding assistants, research agents, workflow copilots — do not answer in one shot. They plan, call tools, read results, and iterate, often for tens of seconds. Users tolerate that wait only if they can *see* the work happening: which tool is running, what it was asked, what came back. That transparency layer is the agent trace UI, and every serious agent product (Claude Code, ChatGPT agent mode, Perplexity Pro search, Devin) has converged on the same pattern this snippet implements: a vertical timeline of steps with live running states, tool-name badges, expandable input/result payloads, per-step durations, and a final highlighted answer card.
The timeline structure: connector lines without an extra element
Each step is a flex row: a 28px icon tile on the left and the step content on the right. The vertical connector between steps is not a separate element — it is a ::before pseudo-element on each .step, absolutely positioned at left: 13px (the horizontal centre of the icon tile) running from top: 30px (just below the icon) to bottom: -4px (into the next step's padding). The last step suppresses its line with :last-child::before { display: none }. Because the line belongs to the step itself, steps can be appended dynamically and the rail grows automatically — no total-height calculation, no SVG.
Step lifecycle: running → done, driven by scheduled timeouts
The trace is modelled as an array of step objects — icon, display name, tool name, duration, input payload, result payload. The run() function walks this array accumulating an offset: at each step's start time it appends the DOM node with a .running class (indigo border, spinning arc icon via a rotate(360deg) keyframe), and at start-plus-duration it swaps to .done (green check icon, duration stamp). In a real product these two moments correspond exactly to the tool_use and tool_result events your agent loop emits — the demo's setTimeout calls are placeholders for event-stream handlers, which is why the component structure transfers directly to production.
Expandable payloads with the CSS grid-rows trick
Clicking a step row toggles its detail panel — the tool's input JSON and result. The expand/collapse animation uses the modern grid-template-rows: 0fr → 1fr technique: the detail wrapper is a single-track grid whose row animates between zero and full content height, with an inner overflow: hidden element doing the clipping. Unlike the old max-height hack, this animates to the *true* content height regardless of payload size, so short and long JSON blobs both animate smoothly. The chevron rotates 90° in sync via a transform transition. Input payloads render in slate mono text; results render in green (.detail-box.result pre), the universal trace-viewer convention for distinguishing what went in from what came out.
Status header, elapsed clock, and the answer reveal
The header carries a pulsing indigo dot (scale + opacity keyframes) and a live status line that mirrors the current step's name — the equivalent of the "thinking…" ticker in commercial agents. A 100ms setInterval updates the elapsed readout from performance.now() deltas, formatted with font-variant-numeric: tabular-nums so digits don't jitter. When the final step completes, the dot turns green and stops pulsing, the status summarises the run ("Done — 4 steps, 2 tools"), and the answer card — a gradient-tinted, indigo-bordered panel — fades and lifts in with an opacity/translateY transition. Separating *trace* from *answer* visually matters: the trace is evidence, the answer is the deliverable, and users need to find the latter instantly on return visits.
Every timer handle is collected into an array and cleared at the top of run(), so the Replay button can restart the trace mid-flight without orphaned timeouts corrupting the new run — the same discipline you need when a user cancels a real agent run and starts another.
Build with AI
Build, Understand, Optimize, and Extend It With AI
The gap between this demo and a production agent trace is one integration layer, and an AI assistant can bridge it for you. Paste the snippet into Claude and ask it to replace the setTimeout scheduler with handlers for a real Anthropic streaming response — it will show you exactly where content_block_start maps to buildStep() and where your tool execution result fills the green pre. Ask it to add the states the demo omits: an error variant with retry counts, payload truncation with "show more", auto-scroll that follows the newest step, and a post-run collapse toggle summarising the trace. If you're debugging your own agent, do the reverse — ask the assistant to write a function that serialises your agent framework's run log (LangChain callbacks, Vercel AI SDK steps, or raw API events) into this snippet's TRACE shape so you get an instant replay viewer for any historical run. The timeline CSS, grid-rows expander, and timer bookkeeping are the parts worth interrogating line by line; ask why each exists and what breaks without it.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build an AI agent tool-call trace timeline in plain HTML, CSS, and JavaScript that replays a multi-step agent run with live states — no frameworks.
Requirements:
- Model the run as an array of step objects: a human-readable name, an optional tool name shown as a monospace badge pill, a duration in milliseconds, an optional pretty-printed JSON input payload, and a result payload.
- Render steps as a vertical timeline where each step has an icon tile and the connector line between steps is a ::before pseudo-element on the step itself, so the rail grows automatically as steps are appended and the last step suppresses its segment.
- Schedule the replay so each step first appears in a "running" state (accent border, continuously spinning arc icon via CSS keyframes, and the header status text mirroring the step name), then flips to "done" (green check icon, duration stamped in tabular figures) after its duration elapses.
- Make each step row clickable to expand a detail panel showing the Input and Result payloads in labelled boxes — results tinted green, inputs neutral — using the grid-template-rows 0fr to 1fr technique with an inner overflow-hidden wrapper so panels animate to their true content height, with a chevron that rotates in sync.
- Show a header with a pulsing status dot and a live elapsed-seconds clock driven by performance.now() on a 100ms interval; when the final step completes, stop and green the dot, summarise the run ("Done — N steps, M tools"), and fade-and-lift in a visually distinct gradient-bordered answer card separate from the trace.
- Include a replay button, and collect every timeout and interval handle into an array that gets cleared at the start of each run so replaying mid-flight never leaves orphaned timers.
- Comment the code to mark exactly which two moments correspond to real tool_use and tool_result events from a streaming LLM API, so the scheduler can be swapped for live event handlers.Step by step
How to Use
- 1Watch the trace and inspect the payloadsThe agent runs automatically: each step appears with a spinning icon and indigo border while "running", then flips to a green check with its duration stamped. Click any step row to expand its Input and Result payloads — the chevron rotates and the panel animates open to its natural height. When the last step finishes, the header dot turns green and the answer card fades in. Click "Replay trace" to run it again.
- 2Drive it from real agent eventsReplace the setTimeout scheduling in run() with your event stream. On a tool_use event: stepsEl.appendChild(buildStep({ icon: "tool", name: ev.label, tool: ev.name, input: JSON.stringify(ev.input, null, 2) })) and add the .running class. On the matching tool_result event: swap .running for .done, inject the check icon, fill .step-time, and set the result pre. The Anthropic and OpenAI streaming APIs both emit exactly these paired events.
- 3Edit the demo traceThe TRACE array defines each step: icon (think/search/tool/file — keys into the ICONS map), name (the human label), tool (the monospace badge, or null for reasoning steps), dur (milliseconds), input (pretty-printed JSON string, or null), and result. Add steps or swap the scenario — a coding agent might use read_file, run_tests, edit_file; a research agent web_search, fetch_page, summarise.
- 4Add error and retry statesReal tools fail. Add a .step.error variant: red border on the icon tile (#f87171), an X icon, and a result box styled with color: #fca5a5. If your agent retries, append a second step with the same tool badge rather than mutating the failed one — traces should be append-only so users can audit what actually happened. A "3 retries" count in .step-time communicates flakiness honestly.
- 5Collapse the trace behind a summary toggleLong agent runs produce 20+ steps. Wrap .steps in the same grid-template-rows: 0fr/1fr container used for step details, collapsed by default once the run completes, with a toggle row reading "Ran 23 steps in 41s". Users who trust the agent see only the answer; users who want the evidence expand the trace. This is the pattern Perplexity and ChatGPT both settled on.
- 6Export and compose with chatClick JSX for React. Render steps from an array in state, keyed by a stable step id, and let CSS handle the running/done transitions — do not animate with JS in React. This component slots between a user message and the streamed answer: pair it with the AI Streaming Response for the answer body, the AI Thinking Loader for pre-planning wait, and the AI Chat Interface shell.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Both APIs emit paired events you map directly onto this UI. With the Anthropic API, a streamed response containing tool use gives you a content_block_start with type tool_use (append a running step: the block's name fills the tool badge, and accumulate input_json deltas into the Input pre), and after you execute the tool and continue the conversation, render your tool_result as the step's Result and mark it done. With OpenAI, the equivalent is the function-call deltas followed by your function response. The key structural decision this snippet already makes for you: buildStep() renders one step from a plain object, so your event handlers just construct that object incrementally — no timeline-wide re-render needed.
The classic max-height transition requires guessing a ceiling (max-height: 500px) — too low clips long payloads, too high makes short panels animate with a visible delay because the transition interpolates through hundreds of unused pixels. Animating grid-template-rows from 0fr to 1fr sidesteps this entirely: the fr unit resolves to the row's actual content height, so a 3-line result and a 40-line result both animate edge-to-edge in exactly 0.28s. The one requirement is an inner wrapper with overflow: hidden (here .step-detail-inner), because the grid track clips at the track level, not the content level. Browser support has been universal since 2023.
Three practices from production agent UIs: collapse by default after completion (wrap the .steps container in the same 0fr/1fr grid trick with a "Ran N steps in Xs" toggle row), auto-scroll during the run (call el.scrollIntoView({ block: "nearest", behavior: "smooth" }) on each newly appended step so the newest work stays visible), and truncate payloads (render the first ~15 lines of a result with a "show all" link, since a 400-line tool output inside an animated panel hurts both performance and readability). If traces exceed ~100 steps, render only the last 30 with a "show earlier steps" button — the DOM cost of hidden expanded panels adds up.
Yes. In Tailwind, the card is bg-slate-800 border border-slate-700 rounded-2xl; icon tiles are size-7 rounded-lg border flex items-center justify-center with data-[state=running]:border-indigo-500 data-[state=done]:text-green-400 variants; the connector line needs a small arbitrary-value utility (before:absolute before:left-[13px] before:top-[30px] before:-bottom-1 before:w-0.5 before:bg-slate-700); and the expander maps to grid grid-rows-[0fr] data-[open]:grid-rows-[1fr] transition-[grid-template-rows]. In Angular, model the trace as a signal array of step objects, render with @for tracked by step id, drive running/done via a class binding, and use the native animation-friendly CSS as-is — no Angular animations module required since all transitions are class-toggled CSS.