Installation
Complete project setup first. Your configuration
must select react/base and include your Design System ID.
pnpm exec frontfriend download trace -f react --primitive baseReview 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 keepMounted. 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>; Link
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 Base UI.
TraceCollapsible
| Prop | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | false | The initial open state when uncontrolled. |
disabled | boolean | false | Disable changes through the trigger. |
onOpenChange | NonNullable<CollapsiblePrimitive.Root.Props["onOpenChange"]> | — | 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. |
keepMounted | 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.