# Plan

The steps the agent plans to take, and how far it has got, under one line with a progress count.

Source: https://suiss.app/ui/docs/ai/plan

**React**

**Example**

```tsx
import {
  CheckCircleIcon,
  CircleIcon,
  PathIcon,
  RecordIcon,
} from "@phosphor-icons/react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceDescription,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const steps = [
  { title: "Install @base-ui/react", status: "done" },
  { title: "Replace the Radix imports", status: "done" },
  { title: "Rename asChild to render", status: "running" },
  { title: "Run the type checker", status: "pending" },
] as const

function StepIcon({ status }: { status: (typeof steps)[number]["status"] }) {
  if (status === "done") {
    return (
      <CheckCircleIcon />
    )
  }

  if (status === "running") {
    return (
      <RecordIcon />
    )
  }

  return (
    <CircleIcon />
  )
}

export default function PlanDemo() {
  const done = steps.filter((step) => step.status === "done").length

  return (
    <div className="flex w-full max-w-sm flex-col gap-4 py-6 text-sm">
      <Message align="end">
        <MessageContent>
          <Bubble align="end" variant="muted">
            <BubbleContent>
              Migrate the dialog components to Base UI.
            </BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageContent className="gap-4">
          <Trace>
            <TraceCollapsible defaultOpen>
              <TraceCollapsibleTrigger>
                <TraceLine status="running">
                  <TraceIcon>
                    <PathIcon />
                  </TraceIcon>
                  <TraceTitle>Plan: migrate to Base UI</TraceTitle>
                  <TraceDescription>
                    {done} of {steps.length} done
                  </TraceDescription>
                </TraceLine>
              </TraceCollapsibleTrigger>
              <TraceCollapsibleContent>
                <Trace>
                  {steps.map((step) => (
                    <TraceLine
                      key={step.title}
                      status={step.status === "done" ? undefined : step.status}
                    >
                      <TraceIcon>
                        <StepIcon status={step.status} />
                      </TraceIcon>
                      <TraceTitle>{step.title}</TraceTitle>
                    </TraceLine>
                  ))}
                </Trace>
              </TraceCollapsibleContent>
            </TraceCollapsible>
          </Trace>
        </MessageContent>
      </Message>
    </div>
  )
}
```

Plan is the list of steps the agent decides on before it starts, such as the steps of a migration. Each step is done, running or pending, and the title counts how many are done.

It is built with [Trace](https://suiss.app/ui/docs/components/trace): a `TraceCollapsible` for the plan and a nested `Trace` for its steps. When the steps are not known up front, use [Task](https://suiss.app/ui/docs/ai/task) instead.

## Installation

Plan is built with [Trace](https://suiss.app/ui/docs/components/trace).

```bash title="Terminal"
npx shadcn@latest add @esuiss/trace
```

Or use the [suiss/ui CLI](https://suiss.app/ui/docs/cli):

```bash title="Terminal"
npx @esuiss/ui@latest add trace
```

## Usage

```tsx
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceDescription,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"
```

```tsx
<Trace>
  <TraceCollapsible defaultOpen>
    <TraceCollapsibleTrigger>
      <TraceLine status="running">
        <TraceIcon>
          <RouteIcon />
        </TraceIcon>
        <TraceTitle>Plan: migrate to Base UI</TraceTitle>
        <TraceDescription>2 of 5 done</TraceDescription>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>
      <Trace>
        <TraceLine>
          <TraceIcon>
            <CircleCheckIcon />
          </TraceIcon>
          <TraceTitle>Replace the Radix imports</TraceTitle>
        </TraceLine>
        <TraceLine status="running">
          <TraceIcon>
            <CircleDotIcon />
          </TraceIcon>
          <TraceTitle>Rename asChild to render</TraceTitle>
        </TraceLine>
        <TraceLine status="pending">
          <TraceIcon>
            <CircleIcon />
          </TraceIcon>
          <TraceTitle>Run the type checker</TraceTitle>
        </TraceLine>
      </Trace>
    </TraceCollapsibleContent>
  </TraceCollapsible>
</Trace>
```

Mark finished steps with no status, the current step with `running` and the steps still to come with `pending`. Keep the progress count in a `TraceDescription` so the title itself stays the goal.

## Examples

### Details

A description above the steps, inside `TraceCollapsibleContent`.

**Example**

```tsx
import { CheckCircleIcon, CircleIcon, PathIcon } from "@phosphor-icons/react"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceDescription,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const steps = [
  "Add a dark variant to the theme tokens",
  "Read the theme from a cookie on the server",
  "Add a toggle to the settings page",
  "Check contrast on the charts",
]

export default function PlanDescription() {
  return (
    <Trace className="max-w-sm">
      <TraceCollapsible defaultOpen>
        <TraceCollapsibleTrigger>
          <TraceLine>
            <TraceIcon>
              <PathIcon />
            </TraceIcon>
            <TraceTitle>Plan: add dark mode</TraceTitle>
            <TraceDescription>1 of {steps.length} done</TraceDescription>
          </TraceLine>
        </TraceCollapsibleTrigger>
        <TraceCollapsibleContent className="flex flex-col gap-3">
          <p>
            Store the theme in a cookie so the server renders the right one and
            the page does not flash on load.
          </p>
          <Trace>
            {steps.map((step, index) => (
              <TraceLine key={step} status={index > 0 ? "pending" : undefined}>
                <TraceIcon>
                  {index === 0 ? (
                    <CheckCircleIcon />
                  ) : (
                    <CircleIcon />
                  )}
                </TraceIcon>
                <TraceTitle>{step}</TraceTitle>
              </TraceLine>
            ))}
          </Trace>
        </TraceCollapsibleContent>
      </TraceCollapsible>
    </Trace>
  )
}
```

## AI SDK

Stream the plan as a data part with a fixed `id`. The AI SDK replaces a data part with the same `id`, so the plan updates in place as steps finish.

```ts title="app/api/chat/route.ts"
const stream = createUIMessageStream({
  execute: async ({ writer }) => {
    writer.write({ type: "data-plan", id: "plan", data: { title, steps } })
  },
})
```

```tsx
const plan = message.parts.find((part) => part.type === "data-plan")
```

## API Reference

See [Trace](https://suiss.app/ui/docs/components/trace#api-reference).

## Accessibility

- The plan line is a button with `aria-expanded`, and its accessible name includes the progress count.
- Pending steps are muted and the running step shimmers, so the step icons also differ by state.

**React Native, SwiftUI, Jetpack Compose**

```tsx
import { CheckCircleIcon, CircleIcon, PathIcon, RecordIcon } from "phosphor-react-native"
import { Icon } from "@/components/ui/icon"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceDescription, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function AiPlanExample() {
  return (
    <TraceCollapsible defaultOpen>
      <TraceCollapsibleTrigger>
        <TraceLine status="running">
          <TraceIcon>
            <Icon as={PathIcon} />
          </TraceIcon>
          <TraceTitle>Plan: migrate to Base UI</TraceTitle>
          <TraceDescription>2 of 4 done</TraceDescription>
        </TraceLine>
      </TraceCollapsibleTrigger>
      <TraceCollapsibleContent>
        <Trace>
          <TraceLine>
            <TraceIcon>
              <Icon as={CheckCircleIcon} />
            </TraceIcon>
            <TraceTitle>Install @base-ui/react</TraceTitle>
          </TraceLine>
          <TraceLine>
            <TraceIcon>
              <Icon as={CheckCircleIcon} />
            </TraceIcon>
            <TraceTitle>Replace the Radix imports</TraceTitle>
          </TraceLine>
          <TraceLine status="running">
            <TraceIcon>
              <Icon as={RecordIcon} />
            </TraceIcon>
            <TraceTitle>Rename asChild to render</TraceTitle>
          </TraceLine>
          <TraceLine status="pending">
            <TraceIcon>
              <Icon as={CircleIcon} />
            </TraceIcon>
            <TraceTitle>Run the type checker</TraceTitle>
          </TraceLine>
        </Trace>
      </TraceCollapsibleContent>
    </TraceCollapsible>
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct PlanExample: View {
  var body: some View {
    TraceCollapsible(initiallyExpanded: true) {
      TraceCollapsibleTrigger {
        TraceLine(status: .running) {
          TraceIcon { Ph.path.regular }
          TraceTitle(verbatim: "Plan: migrate to Base UI")
          TraceDescription(verbatim: "2 of 4 done")
        }
      }
      TraceCollapsibleContent {
        Trace {
          TraceLine {
            TraceIcon { Ph.checkCircle.regular }
            TraceTitle(verbatim: "Install @base-ui/react")
          }
          TraceLine {
            TraceIcon { Ph.checkCircle.regular }
            TraceTitle(verbatim: "Replace the Radix imports")
          }
          TraceLine(status: .running) {
            TraceIcon { Ph.record.regular }
            TraceTitle(verbatim: "Rename asChild to render")
          }
          TraceLine(status: .pending) {
            TraceIcon { Ph.circle.regular }
            TraceTitle(verbatim: "Run the type checker")
          }
        }
      }
    }
  }
}
```

```kotlin
import androidx.compose.runtime.Composable
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.Path
import com.adamglin.phosphoricons.regular.Record
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

@Composable
fun PlanAIDefault() {
    TraceCollapsible(initiallyExpanded = true) {
        TraceCollapsibleTrigger {
            TraceLine(status = TraceStatus.Running) {
                TraceIcon { Icon(PhosphorIcons.Regular.Path, contentDescription = null) }
                TraceTitle("Plan: migrate to Base UI")
                TraceDescription("2 of 4 done")
            }
        }
        TraceCollapsibleContent {
        Trace {
            TraceLine {
                TraceIcon { Icon(PhosphorIcons.Regular.CheckCircle, contentDescription = null) }
                TraceTitle("Install @base-ui/react")
            }
            TraceLine {
                TraceIcon { Icon(PhosphorIcons.Regular.CheckCircle, contentDescription = null) }
                TraceTitle("Replace the Radix imports")
            }
            TraceLine(status = TraceStatus.Running) {
                TraceIcon { Icon(PhosphorIcons.Regular.Record, contentDescription = null) }
                TraceTitle("Rename asChild to render")
            }
            TraceLine(status = TraceStatus.Pending) {
                TraceIcon { Icon(PhosphorIcons.Regular.Circle, contentDescription = null) }
                TraceTitle("Run the type checker")
            }
        }
        }
    }
}
```

Plan on mobile is built from the same parts as on the web: [Trace](https://suiss.app/ui/docs/components/trace). The example above is a static demo; connect it to your agent by rendering the same parts from your messages.

## Installation

**React Native**

```bash title="Terminal"
npx @esuiss/ui@latest add trace
```

**SwiftUI**

```bash title="Terminal"
npx @esuiss/ui@latest add trace --platform swift
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add trace --platform kotlin
```

## API Reference

See [Trace](https://suiss.app/ui/docs/components/trace).

