Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new

Trace

Trace lists the steps of a task. Compose TraceLine, TraceIcon, TraceTitle and TraceDescription inside Trace. Use TraceCollapsible, TraceCollapsibleTrigger and TraceCollapsibleContent for details or nested steps.

Interactive preview

Rendered from the shared Frontfriend stories with React Base, the same preview Studio shows. Styling comes from your Design System; the code below is native to Radix UI.

Installation

Complete project setup first. Your configuration must select react/radix and include your Design System ID.

pnpm exec frontfriend download trace -f react --primitive radix

Review dependencies reported by the downloader and keep your source changes in version control.

Usage

import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleTrigger,
  TraceLine,
  TraceIcon,
  TraceTitle,
  TraceDescription,
  TraceCollapsibleContent,
} from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
import * as React from "react";
export default function TraceExample() {
  const [open, setOpen] = React.useState(true);
  const status = {
    done: undefined,
    running: "running",
    error: "error",
    pending: "pending",
  } as const;
  const label = {
    done: "Complete",
    running: "Running",
    error: "Error",
    pending: "Pending",
  };
  return (
    <div style={{ width: "min(32rem, calc(100vw - 4rem))" }}>
      <Trace>
        <TraceCollapsible
          open={open}
          onOpenChange={setOpen}
          disabled={false}
        >
          <TraceCollapsibleTrigger>
            <TraceLine status={status["running"]}>
              <TraceIcon>
                <Icon name="Search" />
              </TraceIcon>
              <TraceTitle>
                {label["running"]}: {"Inspect sources"}
              </TraceTitle>
              <TraceDescription>2 files</TraceDescription>
            </TraceLine>
          </TraceCollapsibleTrigger>
          <TraceCollapsibleContent>
            The task inspected two source files.
          </TraceCollapsibleContent>
        </TraceCollapsible>
      </Trace>
    </div>
  );
}

Status

Set status to running, error or pending. Omit it for a normal or completed step. Include status in the visible text. Running uses a shimmer; reduced motion turns it off. Trace does not store tasks or infer a parent status.

Composition

A line is a span. Put it inside an anchor or an overlay trigger to make it interactive. The trigger owns keyboard and focus behavior. The outer panel keeps its native height animation. Icons are decorative by default.

Controlled state

Use open, defaultOpen, and onOpenChange on TraceCollapsible. A controlled open value requires onOpenChange. For automatic expansion while running, let a user choice override the running value. Use the binding in the Running Override example. TraceCollapsibleContent supports forceMount. Its className styles the inner wrapper.

Examples

Default

import {
  Trace,
  TraceLine,
  TraceIcon,
  TraceTitle,
  TraceDescription,
} from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
function Steps() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: read the request</TraceTitle>
        <TraceDescription>0.4 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Search" />
        </TraceIcon>
        <TraceTitle>Complete: searched four files</TraceTitle>
        <TraceDescription>1.2 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: prepared the answer</TraceTitle>
      </TraceLine>
    </Trace>
  );
}
<div style={{ width: "min(32rem, calc(100vw - 4rem))" }}>
  <Steps />
</div>;

Statuses

import { Trace, TraceLine, TraceIcon, TraceTitle } from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
<Trace>
  <TraceLine>
    <TraceIcon>
      <Icon name="Check" />
    </TraceIcon>
    <TraceTitle>Complete: request received</TraceTitle>
  </TraceLine>
  <TraceLine status="running">
    <TraceIcon>
      <Icon name="Search" />
    </TraceIcon>
    <TraceTitle>Running: searching files</TraceTitle>
  </TraceLine>
  <TraceLine status="error">
    <TraceIcon>
      <Icon name="X" />
    </TraceIcon>
    <TraceTitle>Error: source is unavailable</TraceTitle>
  </TraceLine>
  <TraceLine status="pending">
    <TraceIcon>
      <Icon name="Clock" />
    </TraceIcon>
    <TraceTitle>Pending: prepare the answer</TraceTitle>
  </TraceLine>
</Trace>;

Default Open

import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleTrigger,
  TraceLine,
  TraceIcon,
  TraceTitle,
  TraceDescription,
  TraceCollapsibleContent,
} from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
<Trace>
  <TraceCollapsible defaultOpen>
    <TraceCollapsibleTrigger>
      <TraceLine>
        <TraceIcon>
          <Icon name="Search" />
        </TraceIcon>
        <TraceTitle>Complete: inspected sources</TraceTitle>
        <TraceDescription>3 files</TraceDescription>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>
      Read the component source, its style rules and the tests.
    </TraceCollapsibleContent>
  </TraceCollapsible>
</Trace>;

Nested

import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleTrigger,
  TraceLine,
  TraceTitle,
  TraceCollapsibleContent,
  TraceIcon,
  TraceDescription,
} from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
function Steps() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: read the request</TraceTitle>
        <TraceDescription>0.4 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Search" />
        </TraceIcon>
        <TraceTitle>Complete: searched four files</TraceTitle>
        <TraceDescription>1.2 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: prepared the answer</TraceTitle>
      </TraceLine>
    </Trace>
  );
}
<Trace>
  <TraceCollapsible defaultOpen>
    <TraceCollapsibleTrigger>
      <TraceLine status="running">
        <TraceTitle>Running: review sources</TraceTitle>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>
      <Trace>
        <TraceCollapsible defaultOpen>
          <TraceCollapsibleTrigger>
            <TraceLine>
              <TraceTitle>Complete: inspect files</TraceTitle>
            </TraceLine>
          </TraceCollapsibleTrigger>
          <TraceCollapsibleContent>
            <Steps />
          </TraceCollapsibleContent>
        </TraceCollapsible>
        <TraceLine status="pending">
          <TraceTitle>Pending: write the report</TraceTitle>
        </TraceLine>
      </Trace>
    </TraceCollapsibleContent>
  </TraceCollapsible>
</Trace>;

Running Override

import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleTrigger,
  TraceLine,
  TraceTitle,
  TraceCollapsibleContent,
} from "@/components/ui/trace";
import { Button } from "@/components/ui/button";
import * as React from "react";
export default function TraceExample() {
  const [running, setRunning] = React.useState(true);
  const [open, setOpen] = React.useState<boolean | undefined>(undefined);
  return (
    <div
      style={{
        display: "grid",
        gap: "1rem",
        width: "min(32rem, calc(100vw - 4rem))",
      }}
    >
      <Trace>
        <TraceCollapsible open={open ?? running} onOpenChange={setOpen}>
          <TraceCollapsibleTrigger>
            <TraceLine status={running ? "running" : undefined}>
              <TraceTitle>
                {running
                  ? "Running: inspect files"
                  : "Complete: inspected files"}
              </TraceTitle>
            </TraceLine>
          </TraceCollapsibleTrigger>
          <TraceCollapsibleContent>
            Close this panel while the task runs. It stays closed when the task
            state changes.
          </TraceCollapsibleContent>
        </TraceCollapsible>
      </Trace>
      <Button variant="outline" onClick={() => setRunning((value) => !value)}>
        {running ? "Finish task" : "Run task"}
      </Button>
      <Button variant="ghost" onClick={() => setOpen(undefined)}>
        Reset user choice
      </Button>
    </div>
  );
}

Long Labels

import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleTrigger,
  TraceLine,
  TraceIcon,
  TraceTitle,
  TraceDescription,
  TraceCollapsibleContent,
} from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
<div style={{ width: "min(22rem, calc(100vw - 4rem))" }}>
  <Trace>
    <TraceCollapsible defaultOpen>
      <TraceCollapsibleTrigger>
        <TraceLine>
          <TraceIcon>
            <Icon name="Search" />
          </TraceIcon>
          <TraceTitle>
            Complete: searched every source file in the selected workspace for
            the requested component
          </TraceTitle>
          <TraceDescription>12.5 s</TraceDescription>
        </TraceLine>
      </TraceCollapsibleTrigger>
      <TraceCollapsibleContent>
        src/features/reports/components/QuarterlyRevenueBreakdownChartWithExtraLongName.tsx
      </TraceCollapsibleContent>
    </TraceCollapsible>
  </Trace>
</div>;
import { Trace, TraceLine, TraceIcon, TraceTitle } from "@/components/ui/trace";
import { Icon } from "@/components/ui/icon";
<Trace>
  <a href="#trace-source">
    <TraceLine>
      <TraceIcon>
        <Icon name="FileText" />
      </TraceIcon>
      <TraceTitle>Complete: read source</TraceTitle>
    </TraceLine>
  </a>
  <span id="trace-source" className="text-neutral-strong">
    Source: local component files.
  </span>
</Trace>;

In Dialog

import {
  Trace,
  TraceLine,
  TraceTitle,
  TraceIcon,
  TraceDescription,
} from "@/components/ui/trace";
import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogBody,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
function Steps() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: read the request</TraceTitle>
        <TraceDescription>0.4 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Search" />
        </TraceIcon>
        <TraceTitle>Complete: searched four files</TraceTitle>
        <TraceDescription>1.2 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: prepared the answer</TraceTitle>
      </TraceLine>
    </Trace>
  );
}
<Trace>
  <Dialog>
    <DialogTrigger asChild>
      <Button variant="ghost">
        <TraceLine>
          <TraceTitle>Open trace in Dialog</TraceTitle>
        </TraceLine>
      </Button>
    </DialogTrigger>
    <DialogContent>
      <DialogHeader>
        <DialogTitle>Task trace</DialogTitle>
        <DialogDescription>Completed task steps.</DialogDescription>
      </DialogHeader>
      <DialogBody>
        <Steps />
      </DialogBody>
    </DialogContent>
  </Dialog>
</Trace>;

In Sheet

import {
  Trace,
  TraceLine,
  TraceTitle,
  TraceIcon,
  TraceDescription,
} from "@/components/ui/trace";
import {
  Sheet,
  SheetTrigger,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetDescription,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
function Steps() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: read the request</TraceTitle>
        <TraceDescription>0.4 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Search" />
        </TraceIcon>
        <TraceTitle>Complete: searched four files</TraceTitle>
        <TraceDescription>1.2 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: prepared the answer</TraceTitle>
      </TraceLine>
    </Trace>
  );
}
<Trace>
  <Sheet>
    <SheetTrigger asChild>
      <Button variant="ghost">
        <TraceLine>
          <TraceTitle>Open trace in Sheet</TraceTitle>
        </TraceLine>
      </Button>
    </SheetTrigger>
    <SheetContent>
      <SheetHeader>
        <SheetTitle>Task trace</SheetTitle>
        <SheetDescription>Completed task steps.</SheetDescription>
      </SheetHeader>
      <Steps />
    </SheetContent>
  </Sheet>
</Trace>;

In Drawer

import {
  Trace,
  TraceLine,
  TraceTitle,
  TraceIcon,
  TraceDescription,
} from "@/components/ui/trace";
import {
  Drawer,
  DrawerTrigger,
  DrawerContent,
  DrawerHeader,
  DrawerTitle,
  DrawerDescription,
  DrawerBody,
} from "@/components/ui/drawer";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
function Steps() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: read the request</TraceTitle>
        <TraceDescription>0.4 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Search" />
        </TraceIcon>
        <TraceTitle>Complete: searched four files</TraceTitle>
        <TraceDescription>1.2 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: prepared the answer</TraceTitle>
      </TraceLine>
    </Trace>
  );
}
<Trace>
  <Drawer>
    <DrawerTrigger asChild>
      <Button variant="ghost">
        <TraceLine>
          <TraceTitle>Open trace in Drawer</TraceTitle>
        </TraceLine>
      </Button>
    </DrawerTrigger>
    <DrawerContent>
      <DrawerHeader>
        <DrawerTitle>Task trace</DrawerTitle>
        <DrawerDescription>Completed task steps.</DrawerDescription>
      </DrawerHeader>
      <DrawerBody>
        <Steps />
      </DrawerBody>
    </DrawerContent>
  </Drawer>
</Trace>;

In Popover

import {
  Trace,
  TraceLine,
  TraceTitle,
  TraceIcon,
  TraceDescription,
} from "@/components/ui/trace";
import {
  Popover,
  PopoverTrigger,
  PopoverContent,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
function Steps() {
  return (
    <Trace>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: read the request</TraceTitle>
        <TraceDescription>0.4 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Search" />
        </TraceIcon>
        <TraceTitle>Complete: searched four files</TraceTitle>
        <TraceDescription>1.2 s</TraceDescription>
      </TraceLine>
      <TraceLine>
        <TraceIcon>
          <Icon name="Check" />
        </TraceIcon>
        <TraceTitle>Complete: prepared the answer</TraceTitle>
      </TraceLine>
    </Trace>
  );
}
<Trace>
  <Popover>
    <PopoverTrigger asChild>
      <Button variant="ghost">
        <TraceLine>
          <TraceTitle>Open trace in Popover</TraceTitle>
        </TraceLine>
      </Button>
    </PopoverTrigger>
    <PopoverContent aria-label="Task trace">
      <Steps />
    </PopoverContent>
  </Popover>
</Trace>;

API reference

Frontfriend-owned props. Native HTML and inherited primitive props remain documented by Radix UI.

TraceCollapsible

Prop Type Default Description
defaultOpen boolean false The initial open state when uncontrolled.
disabled boolean false Disable changes through the trigger.
onOpenChange (open: boolean) => void — Called when the open state changes. Required with open.
open boolean — The controlled open state. Supply onOpenChange when this is set.

TraceCollapsibleContent

Prop Type Default Description
className string — Classes for the inner content wrapper.
forceMount boolean false Keep the content mounted when closed.

TraceLine

Prop Type Default Description
status "running" | "error" | "pending" — The step status. Omit it for a normal or completed step.

Configuration determines the actual classes and available styling hooks. See Theming & component configuration before introducing a repeated local override.