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 ariaReview 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>; 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 {
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.