AI Interface Components

Stack Trace Viewer

A stack trace viewer that lists each frame, highlights your own code while folding library frames, expands a frame into its source snippet, and copies the trace in one click.

A stack trace viewer for AI coding assistants, debugging panels and error report pages. Pass structured frames, or hand a raw stack string to raw — the built-in parseStack understands the two common shapes, at fn (file:line:col) and fn@file:line:col, and keeps any line it cannot parse as plain text. Your own code is marked with a left accent band and a "Your code" tag, while consecutive library frames fold into a single "⋯ 3 library frames" row. Expanding a frame reveals its source snippet, with the failing line tinted and a ^ marker under the column. The button in the top-right corner copies the whole trace, as raw text or Markdown.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add stack-trace-viewer.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

180
Active edited usageDefault props
<StackTraceViewer/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/stack-trace-viewer.en.json

Or, once registries are configured in components.json, install it as @webberui/stack-trace-viewer.

Usage

import { StackTraceViewer } from "@/components/ui/stack-trace-viewer";

// Pass a raw stack string
<StackTraceViewer
  error={{ name: err.name, message: err.message }}
  raw={err.stack}
  onFrameClick={(frame) => openInEditor(frame.file, frame.line)}
/>

// Or pass structured frames (with optional source snippets)
<StackTraceViewer
  error={{ name: "TypeError", message: "Cannot read properties of undefined (reading 'amount')" }}
  frames={[
    {
      fn: "formatPrice",
      file: "src/lib/price.ts",
      line: 42,
      column: 29,
      snippet: { startLine: 41, lines: ["  const rate = item.taxRate ?? 0.05;", "  const off = item.discount.amount;"] },
    },
    { fn: "beginWork", file: "node_modules/ui-runtime/dist/render.js", line: 2210, column: 14 },
  ]}
  copyFormat="markdown"
/>

Props

PropTypeDefaultDescription
errorStackTraceError—Error header: type and message
framesStackFrame[]—Structured stack frames; take precedence over raw
rawstring—Raw stack text, parsed with the built-in parseStack
isUserFrame(frame: StackFrame) => booleansee descriptionDecides whether a frame is user code; by default the file must not contain node_modules, start with node:, or contain internal/
defaultExpanded"first-user" | "none" | "all""first-user"Initial expansion in uncontrolled mode: open the first user frame / collapse all / expand all
expandedstring[]—Controlled list of expanded frame ids; managed internally when omitted
onExpandedChange(ids: string[]) => void—Called when the expanded list changes
collapseLibrarybooleantrueFold runs of consecutive library frames (two or more) into one row
onFrameClick(frame: StackFrame) => void—Called when a frame row is clicked (or activated with Enter/Space), e.g. to open it in an editor
copyFormat"raw" | "markdown""raw"Format produced by the copy button
maxHeightnumber | string420Max height of the frame list before it scrolls internally; numbers are px
durationnumber180Expand animation duration (ms), also written to --wb-stack-trace-viewer-duration
classNamestring—Passed through to the outermost container

StackFrame

FieldTypeDefaultDescription
idstring—Stable identifier; generated from the index when omitted (frame-0, frame-1, …) — this is the value expanded uses
fnstring—Function name; shows "(anonymous)" when missing
filestring—File path
linenumber—Line number (1-based)
columnnumber—Column (1-based), used to draw the ^ marker on the failing line
isUserboolean—Explicitly marks the frame as user code; falls back to isUserFrame when omitted
snippetStackFrameSnippet—Source snippet; the expanded area shows "No source available" when missing
textstring—A raw line that could not be parsed (file is an empty string and line is 0); renders as a plain-text row

StackFrameSnippet

FieldTypeDefaultDescription
startLinenumber—Line number of the snippet's first line in the source file
linesstring[]—Snippet content, one element per line

StackTraceError

FieldTypeDefaultDescription
namestring—Error type, e.g. TypeError
messagestring—Error message; may span multiple lines

The module also exports parseStack(raw), formatStackTrace(error, frames, format), formatFrameLocation(frame) and defaultIsUserFrame(frame), so your server or error-reporting pipeline can reuse the same parsing and formatting logic.

How it works

  • Parsing: the first non-empty line is skipped when it is not a frame (usually the TypeError: … header); any other unparseable line is kept as a plain-text frame instead of silently disappearing.
  • Folding: only runs of two or more library frames fold into one row — folding a lone frame saves no space. Once opened, the frames fade in with a 20ms stagger and focus moves to the first frame of the group.
  • Path truncation: long paths truncate the directory part while the file name and line:col always stay visible, which amounts to a middle truncation; the full path is in the title tooltip.
  • Source snippets are not syntax-highlighted and use only the neutral scale, so they never clash with the code theme your site already has.
  • Copying: on success the icon cross-fades to a check for 1.5s. When the browser denies clipboard access, a read-only field opens with the text already selected and a prompt to press Ctrl+C / ⌘C.
  • Timing variable: --wb-stack-trace-viewer-duration (chevron rotation, default 180ms, written from duration); override the user-frame band color with --wb-stack-trace-viewer-accent.

Accessibility

  • The whole component is a role="region" labelled "Error stack trace", and the frame list uses ordered-list ol semantics
  • Each frame is a native button with aria-expanded and aria-controls, and an aria-label that fully describes it, e.g. "Frame 3: CartSummary, at src/components/cart-summary.tsx line 27, your code"
  • Keyboard: the list uses a roving tabindex and takes a single Tab stop; ↑/↓ move between frames, Home/End jump to the ends, Enter/Space toggle, → expands, ← collapses, and C copies the focused frame's location (Ctrl/⌘+C is left to the browser)
  • The fold rows and the copy button are native buttons; copy results are announced as "Copied" through aria-live="polite"
  • Screen readers additionally hear "failing line" on the error line, while the decorative line-number gutter and ^ marker are hidden from assistive tech
  • When the user has Reduce Motion turned on, panels appear instantly, fold rows expand without the stagger, and the copy icon swaps without a cross-fade