suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
  • AI
GitHubCreate
DocsComponentsPatternsInteractionsChartsBlocksAICreate
Components84
  • Overview
  • Accordion
  • Alert
  • Alert Dialog
  • Alert Sheet
  • Amount Input
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Bottom Bar
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Chip
  • Code Block
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • FAB
  • Field
  • Fullscreen Menu
  • Haptics
  • Header
  • Hover Card
  • Icon
  • Input
  • Input Accessory Bar
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Keyboard Scroll View
  • Keypad
  • Label
  • Marker
  • Menubar
  • Message
  • Message Scroller
  • Native Select
  • Navigation Menu
  • Page Indicator
  • Pagination
  • Pin Pad
  • Popover
  • Progress
  • QR Scanner
  • Questionnaire
  • Radio Group
  • Resizable
  • Screen
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Signature Pad
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Tab Bar
  • Table
  • Tabs
  • Text
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip
  • Trace
  • Wheel Picker
  • Widget

suiss/ui · Component

Trace

Rows for the steps an agent takes: reasoning, tool calls, sources, plans and file edits.

Trace is an open-source component from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.

"use client"

import {
  ArrowUpRightIcon,
  CheckCircleIcon,
  GlobeIcon,
  WarningCircleIcon,
} from "@phosphor-icons/react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,

A Trace is a list of TraceLine rows. Each row has a TraceIcon, a TraceTitle, and an optional TraceDescription and status. A row can expand to show more detail, link to a page, or open a dialog or sheet.

Every row has the same shape, so reasoning, tool calls, sources and plans built from Trace look and behave the same. Use Trace for the steps an agent takes, such as calling a tool, running a subagent or editing a file. Use Message for its reply and Marker for notes and dividers between turns.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/trace

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add trace

Manual

  1. Add Collapsible and the cn helper.

  2. Copy the source into components/ui/trace.tsx:

components/ui/trace.tsx
import { CaretRightIcon } from "@phosphor-icons/react"
import type * as React from "react"

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { cn } from "@/lib/utils"

function Trace({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="trace"
      className={cn(
        "gap-2 flex w-full min-w-0 flex-col *:has-[>[data-slot=trace-line]]:flex *:has-[>[data-slot=trace-line]]:w-fit *:has-[>[data-slot=trace-line]]:max-w-full *:has-[>[data-slot=trace-line]]:min-w-0 *:has-[>[data-slot=trace-line]]:outline-none *:has-[>[data-slot=trace-line]]:rounded-lg *:has-[>[data-slot=trace-line]]:focus-visible:ring-3 *:has-[>[data-slot=trace-line]]:focus-visible:ring-ring/50 *:has-[>[data-slot=trace-line]]:text-start",
  1. Update the import paths to match your project.

Usage

import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"
<Trace>
  <TraceLine>
    <TraceIcon>
      <SearchIcon />
    </TraceIcon>
    <TraceTitle>Searched for minimumReleaseAge</TraceTitle>
  </TraceLine>
</Trace>

Wrap the row in a TraceCollapsible to show more detail when it expands. Put the TraceLine inside TraceCollapsibleTrigger and the detail in TraceCollapsibleContent:

<Trace>
  <TraceCollapsible>
    <TraceCollapsibleTrigger>
      <TraceLine>
        <TraceIcon>
          <TerminalIcon />
        </TraceIcon>
        <TraceTitle>Ran node --version</TraceTitle>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>v24.18.0</TraceCollapsibleContent>
  </TraceCollapsible>
</Trace>

TraceLine is always the row. To make a row do something, wrap it in that thing's trigger: a link, a DialogTrigger, a SheetTrigger, a drawer or a popover.

Rows start closed. Set defaultOpen on TraceCollapsible to start open, or pass open with onOpenChange to control it. Set keepMounted on TraceCollapsibleContent so nested rows keep their open state while the parent is closed.

Rows start closed. Set defaultOpen on TraceCollapsible to start open, or pass open with onOpenChange to control it.

Rows start closed. Set defaultExpanded on TraceCollapsible to start open, or pass isExpanded with onExpandedChange to control it.

Examples

Static

A TraceLine on its own, for a step with no detail to show. It reads as plain text.

import { GitBranchIcon } from "@phosphor-icons/react"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export default function TraceStatic() {
  return (
    <Trace className="max-w-sm">
      <TraceLine>
        <TraceIcon>
          <GitBranchIcon />
        </TraceIcon>
        <TraceTitle>Created branch fix/frozen-lockfile</TraceTitle>
      </TraceLine>
    </Trace>
  )
}

Status

Set status on TraceLine to running, error or pending. Leave it unset for finished work, and say the state in the title too: "Running pnpm build", "Failed to run pnpm lint", "Run pnpm test".

import {
  CheckCircleIcon,
  CircleIcon,
  CircleNotchIcon,
  WarningCircleIcon,
} from "@phosphor-icons/react"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export default function TraceStatus() {
  return (
    <Trace className="max-w-sm">
      <TraceLine>
        <TraceIcon>
          <CheckCircleIcon />
        </TraceIcon>
        <TraceTitle>Ran pnpm install</TraceTitle>

Description

Secondary text such as a diff stat, a duration or a count, after the title. It does not truncate.

import {
  MagnifyingGlassIcon,
  NotePencilIcon,
  TerminalIcon,
} from "@phosphor-icons/react"
import {
  Trace,
  TraceDescription,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

export default function TraceDescriptionDemo() {
  return (
    <Trace className="max-w-sm">

Collapsible

A row that expands to show the command output.

import { TerminalIcon } from "@phosphor-icons/react"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import {
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

export default function TraceCollapsibleDemo() {
  return (
    <div className="w-full max-w-sm">
      <TraceCollapsible>
        <TraceCollapsibleTrigger>

Controlled

Opens and closes a row from outside. To open a row while it streams and still let the reader close it, pass the reader's choice and fall back to the running state.

"use client"

import { TerminalIcon } from "@phosphor-icons/react"
import * as React from "react"

import { Button } from "@/components/ui/button"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import {
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

Link

Wraps the TraceLine in an anchor. For a new tab, use an arrow icon and add "(opens in a new tab)" to the title with sr-only.

import {
  ArrowUpRightIcon,
  CaretRightIcon,
  FileTextIcon,
  GlobeIcon,
} from "@phosphor-icons/react"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export default function TraceLink() {
  return (
    <Trace className="max-w-sm">
      <a href="#link">
        <TraceLine>
          <TraceIcon>
            <FileTextIcon />
          </TraceIcon>

Dialog

Wraps the TraceLine in a DialogTrigger. The dialog owns its open state, Escape and focus return.

import {
  ArrowUpRightIcon,
  CaretRightIcon,
  GlobeIcon,
} from "@phosphor-icons/react"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

const results = [

Sheet

The same shape as the dialog, in a sheet.

import { CaretRightIcon, FolderOpenIcon } from "@phosphor-icons/react"
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export default function TraceSheet() {
  return (
    <Trace className="max-w-sm">
      <Sheet>
        <SheetTrigger>

Group

Rows next to each other in one Trace, which also styles any trigger or link that wraps a TraceLine.

import {
  FileTextIcon,
  MagnifyingGlassIcon,
  TerminalIcon,
} from "@phosphor-icons/react"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export default function TraceGroupDemo() {
  return (
    <Trace className="max-w-sm">
      <TraceLine>
        <TraceIcon>
          <MagnifyingGlassIcon />
        </TraceIcon>
        <TraceTitle>Searched for minimumReleaseAge</TraceTitle>
      </TraceLine>

Sources

A collapsible row whose detail is a Trace of link rows.

import { ArrowUpRightIcon, GlobeIcon } from "@phosphor-icons/react"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const sources = [
  {
    title: "Install Astro | docs.astro.build",
    href: "https://docs.astro.build/en/install-and-setup/",
  },

Steps

A summary row with step rows inside. The parent's status and title come from its children.

import {
  CaretRightIcon,
  FileTextIcon,
  MagnifyingGlassIcon,
} from "@phosphor-icons/react"
import { Badge } from "@/components/ui/badge"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

Commit

A row for each changed file, with the line counts in the description. Each file opens its diff in a sheet.

import {
  CaretRightIcon,
  FilePlusIcon,
  GitCommitIcon,
  NotePencilIcon,
} from "@phosphor-icons/react"
import {
  CodeBlock,
  CodeBlockContent,
  CodeBlockLine,
} from "@/components/ui/code-block"
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,

Whole turn

Three levels: the turn, a summary and the rows. Text between steps sits between two Trace lists.

import {
  BrainIcon,
  CaretRightIcon,
  FileTextIcon,
  TerminalIcon,
} from "@phosphor-icons/react"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceDescription,
  TraceIcon,
  TraceLine,
  TraceTitle,

Reasoning

A thinking row that expands into the reasoning text.

import { BrainIcon } from "@phosphor-icons/react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

export default function TraceThinking() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4 py-6 text-sm">

Tool calls

Tool calls with their input and output, including a failed call.

import { TerminalIcon } from "@phosphor-icons/react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const calls = [
  { command: "node --version", output: "v24.18.0" },

API Reference

Anatomy9 parts
<Trace>
<TraceLine>
<TraceIcon />
<TraceTitle />
<TraceDescription />
</TraceLine>
<TraceCollapsible>
<TraceCollapsibleTrigger>
<TraceLine />
</TraceCollapsibleTrigger>
<TraceCollapsibleContent />
</TraceCollapsible>
</Trace>
01

Trace

A list of rows. It also styles any trigger or link that wraps a TraceLine.

Also accepts the props of React.ComponentProps<"div">.

02

TraceLine

One row: an icon and a title. It is a span, so it is not a button or a tab stop.

PropTypeDefaultDescription
status"running" | "error" | "pending"—The state of the work the row reports. running shimmers the title, error turns it destructive and pending mutes it.

Also accepts the props of React.ComponentProps<"span">.

03

TraceIcon

A decorative icon slot, hidden from assistive tech. Before the title it names the action; after it, it is the indicator.

Also accepts the props of React.ComponentProps<"span">.

04

TraceTitle

One sentence on one line. It truncates, and shimmers while the row is running.

Also accepts the props of React.ComponentProps<"span">.

05

TraceDescription

Secondary text after the title, such as a count or a duration. It does not truncate.

Also accepts the props of React.ComponentProps<"span">.

06

TraceCollapsible

A row that expands.

PropTypeDefaultDescription
openboolean—The controlled open state. Pass it with onOpenChange.
defaultOpenbooleanfalseWhether the row starts open.
onOpenChange(open: boolean) => void—Called when the open state changes.
PropTypeDefaultDescription
isExpandedboolean—The controlled open state. Pass it with onExpandedChange.
defaultExpandedbooleanfalseWhether the row starts open.
onExpandedChange(isExpanded: boolean) => void—Called when the open state changes.

Also accepts the props of Collapsible.

07

TraceCollapsibleTrigger

The button that opens and closes the row. Put a TraceLine inside it; it adds a chevron after the line.

Also accepts the props of CollapsibleTrigger.

08

TraceCollapsibleContent

The detail of a row that expands, indented under the title.

PropTypeDefaultDescription
keepMountedbooleanfalseKeeps the detail mounted while closed, so nested rows keep their open state.

Also accepts the props of CollapsibleContent.

Accessibility

  • Trace adds no roles of its own. Each row gets its semantics from the element that wraps it.
  • A TraceLine on its own is read as text and is not a tab stop.
  • Inside TraceCollapsibleTrigger the row is a button with aria-expanded. Inside a link or a dialog or sheet trigger, it takes that element's keyboard and focus behavior.
  • TraceIcon is hidden from assistive tech, so TraceTitle is the accessible name. Write it as a sentence that says what happened, such as "Edited trace.tsx", not just "trace.tsx".
  • Status is shown with color and motion, so put it in the title too.
  • The running shimmer stops when the reader prefers reduced motion.

Notes

  • Styling hooks: data-slot="trace", "trace-line", "trace-icon", "trace-title", "trace-description", "trace-collapsible", "trace-collapsible-trigger" and "trace-collapsible-content", plus data-status on the row.
  • TraceCollapsibleContent mutes its text. Give a block with a muted background inside it text-foreground to keep its contrast; Code Block sets its own text color.
import { CheckCircleIcon, WarningCircleIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Icon } from "@/components/ui/icon"
import { Message, MessageContent } from "@/components/ui/message"
import { Text } from "@/components/ui/text"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

function Output({ children, error }: { children: string; error?: boolean }) {
  return (
    <CodeBlock size="sm" variant={error ? "destructive" : "default"}>
      <CodeBlockContent wrap>{children}</CodeBlockContent>
    </CodeBlock>
  )
}
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 16) {
      Message(align: .end) {
        MessageContent {
          Bubble(variant: .muted, align: .end) { BubbleContent("Why does pnpm install fail in CI?") }
        }
      }
      Trace {
        TraceCollapsible {
          TraceCollapsibleTrigger {
            TraceLine {
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.CheckCircle
import com.adamglin.phosphoricons.regular.WarningCircle
import com.esuiss.ui.components.Bubble
import com.esuiss.ui.components.BubbleContent
import com.esuiss.ui.components.BubbleVariant
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent

A Trace is a column of TraceLine rows. Each row has a TraceIcon, a TraceTitle and an optional TraceDescription and status. Wrap a row in TraceCollapsible to show more detail when it expands, or in a Pressable to open a link or a sheet.

Use Trace for the steps an agent takes, such as calling a tool, running a command or editing a file. Use Message for its reply and Marker for notes between turns.

Installation

$ pnpm dlx shadcn@latest add @esuiss-rn/trace

See Installation for the components.json setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add trace
$ pnpm dlx @esuiss/ui@latest add trace --platform swift
$ pnpm dlx @esuiss/ui@latest add trace --platform kotlin

The CLI also adds what it builds on: Icon, Text.

Manual

  1. Install the dependencies:
npx expo install cn phosphor-react-native react-native-reanimated react-native-svg react-native-worklets
  1. Add the pieces it builds on: Icon, Text, and the primitives components/primitives/collapsible.tsx, components/primitives/use-render.tsx.

  2. Copy the source into components/ui/trace.tsx:

uniwind/components/ui/trace.tsx
import { cn } from "cn"
import { CaretRightIcon } from "phosphor-react-native"
import * as React from "react"
import { View, type ViewProps } from "react-native"
import Animated, {
  useAnimatedStyle,
  useDerivedValue,
  useReducedMotion,
  useSharedValue,
  withRepeat,
  withTiming,
} from "react-native-reanimated"
import * as CollapsiblePrimitive from "@/components/primitives/collapsible"
import { resolveClassName } from "@/components/primitives/use-render"
import { Icon, IconClassContext } from "@/components/ui/icon"
import { Text, TextClassContext } from "@/components/ui/text"
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install cn phosphor-react-native react-native-reanimated react-native-svg react-native-worklets
  1. Add the pieces it builds on: Icon, Text, and the primitives components/primitives/collapsible.tsx, components/primitives/use-render.tsx.

  2. Copy the source into components/ui/trace.tsx:

nativewind/components/ui/trace.tsx
import { cn } from "cn"
import { CaretRightIcon } from "phosphor-react-native"
import * as React from "react"
import { View, type ViewProps } from "react-native"
import Animated, {
  useAnimatedStyle,
  useDerivedValue,
  useReducedMotion,
  useSharedValue,
  withRepeat,
  withTiming,
} from "react-native-reanimated"
import * as CollapsiblePrimitive from "@/components/primitives/collapsible"
import { resolveClassName } from "@/components/primitives/use-render"
import { Icon, IconClassContext } from "@/components/ui/icon"
import { Text, TextClassContext } from "@/components/ui/text"
  1. Update the import paths to match your project.
  1. Install the dependencies:
npx expo install phosphor-react-native react-native-reanimated react-native-svg react-native-worklets
  1. Add the pieces it builds on: Icon, Text, and the primitives components/primitives/collapsible.tsx, components/primitives/use-render.tsx.

  2. Copy the source into components/ui/trace.tsx:

stylesheet/components/ui/trace.tsx
import { CaretRightIcon } from "phosphor-react-native"
import * as React from "react"
import {
  type GestureResponderEvent,
  StyleSheet,
  View,
  type ViewProps,
} from "react-native"
import Animated, {
  useAnimatedStyle,
  useDerivedValue,
  useReducedMotion,
  useSharedValue,
  withRepeat,
  withTiming,
} from "react-native-reanimated"
  1. Update the import paths to match your project.
  1. Target iOS 17 or later.

  2. Copy the theme files every view uses:

swift/Theme/Theme.swift
import CoreText
import SwiftUI
import UIKit

struct RGBA: Sendable {
  let red: CGFloat
  let green: CGFloat
  let blue: CGFloat
  let alpha: CGFloat

  init(_ hex: UInt32, _ alpha: CGFloat = 1) {
    red = CGFloat((hex >> 16) & 0xFF) / 255
    green = CGFloat((hex >> 8) & 0xFF) / 255
    blue = CGFloat(hex & 0xFF) / 255
    self.alpha = alpha
  }
swift/Theme/Tokens.swift
import SwiftUI

public enum EsuissColor {
  public static let background = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
  public static let foreground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let card = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let cardForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let popover = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
  public static let popoverForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let primary = Color.adaptive(light: .init(0x0071E3), dark: .init(0x2997FF))
  public static let primaryForeground = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0xFFFFFF))
  public static let secondary = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
  public static let secondaryForeground = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
  public static let muted = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
  public static let mutedForeground = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
  public static let accent = Color.adaptive(light: .init(0x000000, 0.06), dark: .init(0xFFFFFF, 0.1))
  1. Copy the source and every file it uses:
swift/Components/Trace.swift
import PhosphorSwift
import SwiftUI

public enum TraceStatus: Sendable {
  case running, error, pending
}

extension EnvironmentValues {
  @Entry var traceStatus: TraceStatus?
}

public struct Trace<Content: View>: View {
  let content: Content

  public init(@ViewBuilder content: () -> Content) {
    self.content = content()
swift/Components/Collapsible.swift
import SwiftUI

public struct Collapsible<Content: View>: View {
  let external: Binding<Bool>?
  let spacing: CGFloat
  let content: Content

  @State private var local = false
  @Environment(\.accessibilityReduceMotion) private var reduceMotion

  public init(
    isExpanded: Binding<Bool>,
    spacing: CGFloat = 0,
    @ViewBuilder content: () -> Content
  ) {
    self.external = isExpanded
  1. Add the files to your app target.
  1. Set minSdk to 24 or higher and add the dependencies:
build.gradle.kts
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
  1. Copy the theme files every view uses:
kotlin/theme/Theme.kt
package com.esuiss.ui.theme

import android.content.res.AssetManager
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
kotlin/theme/Tokens.kt
package com.esuiss.ui.theme

import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp

@Immutable
data class Colors(
    val background: Color,
    val foreground: Color,
    val card: Color,
    val cardForeground: Color,
    val popover: Color,
    val popoverForeground: Color,
    val primary: Color,
  1. Copy the source and every file it uses:
kotlin/components/Trace.kt
package com.esuiss.ui.components

import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
kotlin/components/Button.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
kotlin/components/ButtonGroup.kt
package com.esuiss.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
kotlin/components/Collapsible.kt
package com.esuiss.ui.components

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
kotlin/components/Content.kt
package com.esuiss.ui.components

import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

val LocalContentStyle = compositionLocalOf { TextStyle.Default }

val LocalContentColor = compositionLocalOf { Color.Unspecified }

val LocalIconSize = compositionLocalOf { 20.dp }
kotlin/components/Icon.kt
package com.esuiss.ui.components

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import com.esuiss.ui.theme.EsuissTheme

@Composable
fun Icon(
    imageVector: ImageVector,
  1. Update the com.esuiss.ui package names to match your project.

Usage

import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"
<Trace>
  <TraceCollapsible>
    <TraceCollapsibleTrigger>
      <TraceLine>
        <TraceIcon>
          <Icon as={TerminalIcon} />
        </TraceIcon>
        <TraceTitle>Ran node --version</TraceTitle>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>
      <Text>v24.18.0</Text>
    </TraceCollapsibleContent>
  </TraceCollapsible>
</Trace>

Rows start closed. Set defaultOpen on TraceCollapsible to start open, or pass open with onOpenChange to control it. In SwiftUI use TraceCollapsible(initiallyExpanded:) or TraceCollapsible(isExpanded:); in Compose pass initiallyExpanded or expanded with onExpandedChange.

Examples

Static

A TraceLine on its own, for a step with no detail to show.

import { GitBranchIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function TraceStaticExample() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon as={GitBranchIcon} />
        </TraceIcon>
        <TraceTitle>Created branch fix/frozen-lockfile</TraceTitle>
      </TraceLine>
    </Trace>
  )
}
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceStaticExample: View {
  var body: some View {
    Trace {
      TraceLine {
        TraceIcon { Ph.gitBranch.regular }
        TraceTitle("Created branch fix/frozen-lockfile")
      }
    }
  }
}
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.GitBranch
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceDescription
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceStatus
import com.esuiss.ui.components.TraceTitle

@Composable
fun TraceRow(icon: ImageVector, title: String, status: TraceStatus? = null, description: String? = null) {
    TraceLine(status = status) {

Status

running pulses the title, error turns the row destructive and pending dims it. Say the state in the title too.

import { CheckCircleIcon, CircleIcon, CircleNotchIcon, WarningCircleIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function TraceStatusExample() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon as={CheckCircleIcon} />
        </TraceIcon>
        <TraceTitle>Ran pnpm install</TraceTitle>
      </TraceLine>
      <TraceLine status="error">
        <TraceIcon>
          <Icon as={WarningCircleIcon} />
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceStatusExample: View {
  var body: some View {
    Trace {
      TraceLine {
        TraceIcon { Ph.checkCircle.regular }
        TraceTitle("Ran pnpm install")
      }
      TraceLine(status: .error) {
        TraceIcon { Ph.warningCircle.regular }
        TraceTitle("Failed to run pnpm lint")
      }
      TraceLine(status: .running) {
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.CheckCircle
import com.adamglin.phosphoricons.regular.Circle
import com.adamglin.phosphoricons.regular.CircleNotch
import com.adamglin.phosphoricons.regular.WarningCircle
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceDescription
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceStatus
import com.esuiss.ui.components.TraceTitle

Description

Secondary text such as a diff stat, a duration or a count after the title.

import { MagnifyingGlassIcon, NotePencilIcon, TerminalIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function TraceDescriptionExample() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon as={NotePencilIcon} />
        </TraceIcon>
        <TraceTitle>Edited settings/notifications.tsx</TraceTitle>
        <TraceDescription>+48 −12</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceDescriptionExample: View {
  var body: some View {
    Trace {
      TraceLine {
        TraceIcon { Ph.notePencil.regular }
        TraceTitle("Edited settings/notifications.tsx")
        TraceDescription(verbatim: "+48 −12")
      }
      TraceLine {
        TraceIcon { Ph.terminal.regular }
        TraceTitle("Ran pnpm test")
        TraceDescription(verbatim: "14s")
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.MagnifyingGlass
import com.adamglin.phosphoricons.regular.NotePencil
import com.adamglin.phosphoricons.regular.Terminal
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceDescription
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceStatus
import com.esuiss.ui.components.TraceTitle

@Composable

Collapsible

A row that expands to show the command output.

import { TerminalIcon } from "phosphor-react-native"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Icon } from "@/components/ui/icon"
import { TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

function Output({ children, error }: { children: string; error?: boolean }) {
  return (
    <CodeBlock size="sm" variant={error ? "destructive" : "default"}>
      <CodeBlockContent wrap>{children}</CodeBlockContent>
    </CodeBlock>
  )
}

export function TraceCollapsibleExample() {
  return (
    <TraceCollapsible>
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceCollapsibleExample: View {
  var body: some View {
    TraceCollapsible {
      TraceCollapsibleTrigger {
        TraceLine {
          TraceIcon { Ph.terminal.regular }
          TraceTitle("Ran node --version")
        }
      }
      TraceCollapsibleContent { TraceOutput(text: "$ node --version\nv24.18.0") }
    }
  }
import androidx.compose.runtime.Composable
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Terminal
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockSize
import com.esuiss.ui.components.CodeBlockVariant
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceTitle

Controlled

Opens and closes a row from outside with open and onOpenChange.

import { TerminalIcon } from "phosphor-react-native"
import * as React from "react"
import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Icon } from "@/components/ui/icon"
import { TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

function Output({ children, error }: { children: string; error?: boolean }) {
  return (
    <CodeBlock size="sm" variant={error ? "destructive" : "default"}>
      <CodeBlockContent wrap>{children}</CodeBlockContent>
    </CodeBlock>
  )
}
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceOutput: View {
  let text: String
  var error = false

  var body: some View {
    CodeBlock(error ? .destructive : .default, size: .sm) {
      CodeBlockContent(text, wrap: true)
    }
  }
}

struct TraceControlledExample: View {
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Terminal
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonSize
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent

Link

Wraps the TraceLine in a Pressable that opens a URL.

import { ArrowUpRightIcon, GlobeIcon } from "phosphor-react-native"
import { Linking, Pressable } from "react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

function SourceRow({ title, url }: { title: string; url: string }) {
  return (
    <Pressable
      accessibilityRole="link"
      accessibilityHint="Opens in the browser"
      onPress={() => Linking.openURL(url)}
      className="self-start active:opacity-70"
    >
      <TraceLine>
        <TraceIcon>
          <Icon as={GlobeIcon} />
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceLinkExample: View {
  var body: some View {
    Trace {
      TraceSourceRow(title: "Install Astro | docs.astro.build", url: "https://docs.astro.build/en/install-and-setup/")
    }
  }
}

struct TraceSourceRow: View {
  let title: String
  let url: String
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.semantics.Role
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.ArrowUpRight
import com.adamglin.phosphoricons.regular.Globe
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceTitle

Group

Rows next to each other in one Trace.

import { FileTextIcon, MagnifyingGlassIcon, TerminalIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function TraceGroupExample() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon as={MagnifyingGlassIcon} />
        </TraceIcon>
        <TraceTitle>Searched for minimumReleaseAge</TraceTitle>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon as={FileTextIcon} />
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceGroupExample: View {
  var body: some View {
    Trace {
      TraceLine {
        TraceIcon { Ph.magnifyingGlass.regular }
        TraceTitle("Searched for minimumReleaseAge")
      }
      TraceLine {
        TraceIcon { Ph.fileText.regular }
        TraceTitle("Read pnpm-workspace.yaml")
      }
      TraceLine {
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.FileText
import com.adamglin.phosphoricons.regular.MagnifyingGlass
import com.adamglin.phosphoricons.regular.Terminal
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceDescription
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceStatus
import com.esuiss.ui.components.TraceTitle

@Composable

Sources

A collapsible row whose detail is a Trace of link rows.

import { ArrowUpRightIcon, GlobeIcon } from "phosphor-react-native"
import { Linking, Pressable } from "react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

const sources = [
  { title: "pnpm install | pnpm.io", url: "https://pnpm.io/cli/install" },
  {
    title: "Continuous Integration | pnpm.io",
    url: "https://pnpm.io/continuous-integration",
  },
]

function SourceRow({ title, url }: { title: string; url: string }) {
  return (
    <Pressable
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceSourcesExample: View {
  var body: some View {
    TraceCollapsible(initiallyExpanded: true) {
      TraceCollapsibleTrigger {
        TraceLine {
          TraceIcon { Ph.globe.regular }
          TraceTitle("Used 2 sources")
        }
      }
      TraceCollapsibleContent {
        Trace {
          TraceSourceRow(title: "pnpm install | pnpm.io", url: "https://pnpm.io/cli/install")
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.semantics.Role
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.ArrowUpRight
import com.adamglin.phosphoricons.regular.Globe
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceIcon

Steps

A summary row with step rows inside.

import { FileTextIcon, MagnifyingGlassIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function TraceStepsExample() {
  return (
    <TraceCollapsible defaultOpen>
      <TraceCollapsibleTrigger>
        <TraceLine status="running">
          <TraceTitle>Looking into the slow deploy</TraceTitle>
        </TraceLine>
      </TraceCollapsibleTrigger>
      <TraceCollapsibleContent keepMounted>
        <Trace>
          <TraceLine>
            <TraceIcon>
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceStepsExample: View {
  var body: some View {
    TraceCollapsible(initiallyExpanded: true) {
      TraceCollapsibleTrigger {
        TraceLine(status: .running) { TraceTitle("Looking into the slow deploy") }
      }
      TraceCollapsibleContent {
        Trace {
          TraceLine {
            TraceIcon { Ph.magnifyingGlass.regular }
            TraceTitle("Searched for the checkout build log")
          }
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.FileText
import com.adamglin.phosphoricons.regular.MagnifyingGlass
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceDescription
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceStatus
import com.esuiss.ui.components.TraceTitle

Commit

A row for each changed file, with the line counts in the description.

import { FilePlusIcon, GitCommitIcon, NotePencilIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

const files = [
  { icon: NotePencilIcon, path: "app/login/page.tsx", stat: "+12 −4" },
  { icon: NotePencilIcon, path: "lib/auth/redirect.ts", stat: "+31 −9" },
  { icon: FilePlusIcon, path: "lib/auth/redirect.test.ts", stat: "+58" },
]

export function TraceCommitExample() {
  return (
    <TraceCollapsible defaultOpen>
      <TraceCollapsibleTrigger>
        <TraceLine>
          <TraceIcon>
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceCommitExample: View {
  var body: some View {
    TraceCollapsible(initiallyExpanded: true) {
      TraceCollapsibleTrigger {
        TraceLine {
          TraceIcon { Ph.gitCommit.regular }
          TraceTitle("Committed a1b2c3: Fix the login redirect")
        }
      }
      TraceCollapsibleContent {
        Trace {
          TraceLine {
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.FilePlus
import com.adamglin.phosphoricons.regular.GitCommit
import com.adamglin.phosphoricons.regular.NotePencil
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceDescription
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceStatus

Whole turn

Three levels: the turn, a summary and the rows, with text between the steps.

import { BrainIcon, FolderOpenIcon, TerminalIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Text } from "@/components/ui/text"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function TraceTurnExample() {
  return (
    <TraceCollapsible defaultOpen>
      <TraceCollapsibleTrigger>
        <TraceLine status="error">
          <TraceTitle>Worked for 2m 4s, 1 command failed</TraceTitle>
        </TraceLine>
      </TraceCollapsibleTrigger>
      <TraceCollapsibleContent keepMounted>
        <Trace>
          <TraceLine>
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceTurnExample: View {
  var body: some View {
    TraceCollapsible(initiallyExpanded: true) {
      TraceCollapsibleTrigger {
        TraceLine(status: .error) { TraceTitle("Worked for 2m 4s, 1 command failed") }
      }
      TraceCollapsibleContent {
        Trace {
          TraceLine {
            TraceIcon { Ph.brain.regular }
            TraceTitle("Thought")
            TraceDescription(verbatim: "12s")
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Brain
import com.adamglin.phosphoricons.regular.FolderOpen
import com.adamglin.phosphoricons.regular.Terminal
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceDescription
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine

Reasoning

A thinking row that expands into the reasoning text.

import { BrainIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Text } from "@/components/ui/text"
import { TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function TraceReasoningExample() {
  return (
    <TraceCollapsible defaultOpen>
      <TraceCollapsibleTrigger>
        <TraceLine>
          <TraceIcon>
            <Icon as={BrainIcon} />
          </TraceIcon>
          <TraceTitle>Thought</TraceTitle>
        </TraceLine>
      </TraceCollapsibleTrigger>
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceReasoningExample: View {
  var body: some View {
    TraceCollapsible(initiallyExpanded: true) {
      TraceCollapsibleTrigger {
        TraceLine {
          TraceIcon { Ph.brain.regular }
          TraceTitle("Thought")
        }
      }
      TraceCollapsibleContent {
        Text("The build log shows install and compile finish in under a minute. Image optimization takes almost four, and it reruns on every deploy because the cache key includes the commit hash.")
      }
import androidx.compose.runtime.Composable
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.Brain
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Text
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceTitle

@Composable
fun TraceReasoning() {
    TraceCollapsible(initiallyExpanded = true) {

Tool calls

Tool calls with their output, including a failed call.

import { FileTextIcon, TerminalIcon, WarningCircleIcon } from "phosphor-react-native"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

function Output({ children, error }: { children: string; error?: boolean }) {
  return (
    <CodeBlock size="sm" variant={error ? "destructive" : "default"}>
      <CodeBlockContent wrap>{children}</CodeBlockContent>
    </CodeBlock>
  )
}

const calls = [
  {
    title: "Read package.json",
import EsuissUI
import PhosphorSwift
import SwiftUI

struct TraceToolCallsExample: View {
  var body: some View {
    Trace {
      TraceCollapsible {
        TraceCollapsibleTrigger {
          TraceLine {
            TraceIcon { Ph.fileText.regular }
            TraceTitle(verbatim: "Read package.json")
          }
        }
        TraceCollapsibleContent {
          TraceOutput(text: "{ \"name\": \"acme\", \"packageManager\": \"pnpm@10.18.0\" }")
import androidx.compose.runtime.Composable
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.FileText
import com.adamglin.phosphoricons.regular.WarningCircle
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockSize
import com.esuiss.ui.components.CodeBlockVariant
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine

API Reference

01

Trace

A column of rows.

Also accepts the props of ViewProps.

02

TraceLine

One row: an icon, a title and an optional description.

PropTypeDefaultDescription
status"running" | "error" | "pending"—The state of the work the row reports.

Also accepts the props of ViewProps.

03

TraceIcon

A 16 pt icon slot, hidden from assistive technology.

Also accepts the props of ViewProps.

04

TraceTitle

One line of text that truncates. It pulses while the row is running unless the reader prefers reduced motion.

Also accepts the props of Text.

05

TraceDescription

Secondary text after the title, with tabular numbers.

Also accepts the props of Text.

06

TraceCollapsible

A row that expands.

PropTypeDefaultDescription
openboolean—The controlled open state. Pass it with onOpenChange.
defaultOpenbooleanfalseWhether the row starts open.
onOpenChange(open: boolean) => void—Called when the open state changes.
07

TraceCollapsibleTrigger

The pressable row that opens and closes the detail. It adds a chevron after the line.

08

TraceCollapsibleContent

The detail of a row, indented under the title with a leading rule.

PropTypeDefaultDescription
keepMountedbooleanfalseKeeps the detail mounted while closed, so nested rows keep their state.

Accessibility

  • The trigger is a button whose state reads as expanded or collapsed.
  • TraceIcon is hidden from assistive technology, so write the title as a sentence that says what happened.
  • Status is shown with color and motion, so put it in the title too.
NextWheel PickerPreviousTooltip

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • Static
    • Status
    • Description
    • Collapsible
    • Controlled
    • Link
    • Dialog
    • Sheet
    • Group
    • Sources
    • Steps
    • Commit
    • Whole turn
    • Reasoning
    • Tool calls
  4. 04API Reference
    • Trace
    • TraceLine
    • TraceIcon
    • TraceTitle
    • TraceDescription
    • TraceCollapsible
    • TraceCollapsibleTrigger
    • TraceCollapsibleContent
  5. 05Accessibility
  6. 06Notes
  7. 07Installation
    • Manual
  8. 08Usage
  9. 09Examples
    • Static
    • Status
    • Description
    • Collapsible
    • Controlled
    • Link
    • Group
    • Sources
    • Steps
    • Commit
    • Whole turn
    • Reasoning
    • Tool calls
  10. 10API Reference
    • Trace
    • TraceLine
    • TraceIcon
    • TraceTitle
    • TraceDescription
    • TraceCollapsible
    • TraceCollapsibleTrigger
    • TraceCollapsibleContent
  11. 11Accessibility