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 React Aria.

Installation

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

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

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
          isExpanded={open}
          onExpandedChange={setOpen}
          isDisabled={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 isExpanded, defaultExpanded, and onExpandedChange on TraceCollapsible. A controlled isExpanded value requires onExpandedChange. For automatic expansion while running, let a user choice override the running value. Use the binding in the Running Override example. Use onPress on TraceCollapsibleTrigger. Content 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 defaultExpanded>
    <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 defaultExpanded>
    <TraceCollapsibleTrigger>
      <TraceLine status="running">
        <TraceTitle>Running: review sources</TraceTitle>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>
      <Trace>
        <TraceCollapsible defaultExpanded>
          <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
          isExpanded={open ?? running}
          onExpandedChange={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" onPress={() => setRunning((value) => !value)}>
        {running ? "Finish task" : "Run task"}
      </Button>
      <Button variant="ghost" onPress={() => 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 defaultExpanded>
      <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 {
  DialogTrigger,
  Dialog,
  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>
  <DialogTrigger>
    <Button variant="ghost">
      <TraceLine>
        <TraceTitle>Open trace in Dialog</TraceTitle>
      </TraceLine>
    </Button>
    <Dialog>
      <DialogHeader>
        <DialogTitle>Task trace</DialogTitle>
        <DialogDescription>Completed task steps.</DialogDescription>
      </DialogHeader>
      <DialogBody>
        <Steps />
      </DialogBody>
    </Dialog>
  </DialogTrigger>
</Trace>;

In Sheet

import {
  Trace,
  TraceLine,
  TraceTitle,
  TraceIcon,
  TraceDescription,
} from "@/components/ui/trace";
import {
  Sheet,
  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>
    <Button variant="ghost">
      <TraceLine>
        <TraceTitle>Open trace in Sheet</TraceTitle>
      </TraceLine>
    </Button>
    <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, 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>
    <Button variant="ghost">
      <TraceLine>
        <TraceTitle>Open trace in Popover</TraceTitle>
      </TraceLine>
    </Button>
    <PopoverContent aria-label="Task trace">
      <Steps />
    </PopoverContent>
  </Popover>
</Trace>;

API reference

Frontfriend-owned props. Native HTML and inherited primitive props remain documented by React Aria.

TraceCollapsible

Prop Type Default Description
defaultExpanded boolean false The initial expanded state when uncontrolled.
isDisabled boolean false Disable changes through the trigger.
isExpanded boolean — The controlled expanded state. Supply onExpandedChange when this is set.
onExpandedChange (isExpanded: boolean) => void — Called when the expanded state changes. Required with isExpanded.

TraceCollapsibleContent

Prop Type Default Description
className string — Classes for the inner content wrapper.

TraceCollapsibleTrigger

Prop Type Default Description
onPress ButtonProps["onPress"] — Called when the trigger is pressed.

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.