assistant-ui logo/Docs/Primitives

ChainOfThought

Collapsible accordion for grouping reasoning steps and tool calls.

The ChainOfThought primitive is the legacy accordion API for grouped reasoning and tool-call parts. Reasoning models emit reasoning tokens and tool calls before producing a final answer.

For new grouped reasoning/tool-call UI, use MessagePrimitive.GroupedParts. ChainOfThoughtPrimitive and components.ChainOfThought remain available for maintaining existing code.

AI

The user wants the current weather in Tokyo. I should call the weather tool first.

Got the weather result. I can now answer the user directly.

It's currently 22C and partly cloudy in Tokyo.

Group reasoning and tool-call parts directly in your assistant message:

import { MessagePrimitive, groupPartByType } from "@assistant-ui/react";

<MessagePrimitive.Root>
  <MessagePrimitive.GroupedParts
    groupBy={groupPartByType({
      reasoning: ["group-chainOfThought", "group-reasoning"],
      "tool-call": ["group-chainOfThought", "group-tool"],
    })}
  >
    {({ part, children }) => {
      switch (part.type) {
        case "group-chainOfThought":
          return <ThinkingAccordion>{children}</ThinkingAccordion>;
        case "group-reasoning":
          return <ReasoningGroup>{children}</ReasoningGroup>;
        case "group-tool":
          return <ToolGroup>{children}</ToolGroup>;
        case "text":
          return <MyText />;
        case "reasoning":
          return <MyReasoning {...part} />;
        case "tool-call":
          return part.toolUI ?? <MyToolFallback {...part} />;
        default:
          return null;
      }
    }}
  </MessagePrimitive.GroupedParts>
</MessagePrimitive.Root>

GroupedParts API Reference

Prop

Type

Legacy: ChainOfThoughtPrimitive

Quick Start

Render your normal message parts with MessagePrimitive.Parts, then place a ChainOfThought component alongside them inside the same MessagePrimitive.Root only when maintaining older code that already uses the ChainOfThought primitive.

Runtime setup: primitives require runtime context. Wrap your UI in AssistantRuntimeProvider with a runtime (for example useLocalRuntime(...)). See Pick a Runtime.

Concepts

How Grouping Works

ChainOfThoughtPrimitive.Parts reads the current message's grouped reasoning and tool-call context from the legacy components.ChainOfThought path. New code should use MessagePrimitive.GroupedParts instead.

Collapsed State

The accordion starts collapsed by default. AccordionTrigger toggles between collapsed and expanded. Use AuiIf to conditionally render parts based on the collapsed state:

import { AuiIf, ChainOfThoughtPrimitive } from "@assistant-ui/react";

<ChainOfThoughtPrimitive.Root>
  <ChainOfThoughtPrimitive.AccordionTrigger>
    Thinking
  </ChainOfThoughtPrimitive.AccordionTrigger>
  <AuiIf condition={(s) => !s.chainOfThought.collapsed}>
    <ChainOfThoughtPrimitive.Parts
      components={{ Reasoning }}
    />
  </AuiIf>
</ChainOfThoughtPrimitive.Root>

Chevron Indicators

Use AuiIf to show directional icons that reflect the current state:

import { AuiIf, ChainOfThoughtPrimitive } from "@assistant-ui/react";
import { ChevronDownIcon, ChevronRightIcon } from "lucide-react";

<ChainOfThoughtPrimitive.AccordionTrigger className="flex w-full cursor-pointer items-center gap-2 px-4 py-2 text-sm">
  <AuiIf condition={(s) => s.chainOfThought.collapsed}>
    <ChevronRightIcon className="size-4" />
  </AuiIf>
  <AuiIf condition={(s) => !s.chainOfThought.collapsed}>
    <ChevronDownIcon className="size-4" />
  </AuiIf>
  Thinking
</ChainOfThoughtPrimitive.AccordionTrigger>

Parts Components

ChainOfThoughtPrimitive.Parts accepts a deprecated components prop to control how each part type renders:

<ChainOfThoughtPrimitive.Parts
  components={{
    Reasoning: ({ text }) => (
      <p className="whitespace-pre-wrap px-4 py-2 text-muted-foreground text-sm italic">
        {text}
      </p>
    ),
    tools: {
      Fallback: ({ toolName, status }) => (
        <div className="px-4 py-2 text-sm">
          {status.type === "running" ? `Running ${toolName}...` : `${toolName} completed`}
        </div>
      ),
    },
    Layout: ({ children }) => (
      <div className="border-t">{children}</div>
    ),
  }}
/>
PropTypeDescription
components.ReasoningFC<{ text: string }>Renders reasoning parts
components.tools.FallbackToolCallMessagePartComponentFallback for tool-call parts
components.LayoutComponentType<PropsWithChildren>Wrapper around each rendered part

Parts API Reference

Root

Container for the chain-of-thought disclosure UI. Renders a <div> element unless asChild is set.

<ChainOfThoughtPrimitive.Root className="rounded-lg border">
  ...
</ChainOfThoughtPrimitive.Root>

AccordionTrigger

Trigger that toggles the collapsed state. Renders a <button> element unless asChild is set.

<ChainOfThoughtPrimitive.AccordionTrigger className="flex w-full items-center justify-between px-4 py-2 text-sm">
  Thinking
</ChainOfThoughtPrimitive.AccordionTrigger>

Parts

Renders reasoning and tool-call parts. This component does not track collapsed state internally, so control visibility with AuiIf as shown in the patterns below.

<ChainOfThoughtPrimitive.Parts
  components={{
    Reasoning: ({ text }) => (
      <p className="whitespace-pre-wrap px-4 py-2 text-muted-foreground text-sm italic">
        {text}
      </p>
    ),
    tools: {
      Fallback: ({ toolName, status }) => (
        <div className="px-4 py-2 text-sm">
          {status.type === "running" ? `Running ${toolName}...` : `${toolName} completed`}
        </div>
      ),
    },
  }}
/>

Prop

Type

Patterns

Minimal Accordion

function ChainOfThought() {
  return (
    <ChainOfThoughtPrimitive.Root className="my-2 rounded-lg border">
      <ChainOfThoughtPrimitive.AccordionTrigger className="flex w-full cursor-pointer items-center gap-2 px-4 py-2 font-medium text-sm hover:bg-muted/50">
        Thinking
      </ChainOfThoughtPrimitive.AccordionTrigger>
      <AuiIf condition={(s) => !s.chainOfThought.collapsed}>
        <ChainOfThoughtPrimitive.Parts
          components={{
            Reasoning: ({ text }) => (
              <p className="whitespace-pre-wrap px-4 py-2 text-muted-foreground text-sm italic">
                {text}
              </p>
            ),
          }}
        />
      </AuiIf>
    </ChainOfThoughtPrimitive.Root>
  );
}

With Tool Calls

function ChainOfThought() {
  return (
    <ChainOfThoughtPrimitive.Root className="my-2 rounded-lg border">
      <ChainOfThoughtPrimitive.AccordionTrigger className="flex w-full cursor-pointer items-center gap-2 px-4 py-2 font-medium text-sm hover:bg-muted/50">
        Thinking
      </ChainOfThoughtPrimitive.AccordionTrigger>
      <AuiIf condition={(s) => !s.chainOfThought.collapsed}>
        <ChainOfThoughtPrimitive.Parts
          components={{
            Reasoning: ({ text }) => (
              <p className="whitespace-pre-wrap px-4 py-2 text-muted-foreground text-sm italic">
                {text}
              </p>
            ),
            tools: {
              Fallback: ({ toolName, status }) => (
                <div className="flex items-center gap-2 px-4 py-2 text-sm">
                  <span className="font-medium">{toolName}</span>
                  <span className="text-muted-foreground">
                    {status.type === "running" ? "running..." : "done"}
                  </span>
                </div>
              ),
            },
            Layout: ({ children }) => (
              <div className="border-t">{children}</div>
            ),
          }}
        />
      </AuiIf>
    </ChainOfThoughtPrimitive.Root>
  );
}

Relationship to Components

The Chain of Thought guide covers end-to-end setup with MessagePrimitive.GroupedParts, including backend configuration with reasoning models. See the complete with-chain-of-thought example for a full working implementation.

API Reference

For the complete guide including backend configuration, see Chain of Thought. For prop details, see the ChainOfThoughtPrimitive source.

Related: