Documents
chain-of-thought
chain-of-thought
Type
External
Status
Published
Created
Mar 17, 2026
Updated
Mar 17, 2026

LLMs often produce reasoning steps and tool calls in succession. Chain of Thought lets you visually group these consecutive parts into a single collapsible accordion, giving users a clean "thinking" UI.

Overview#

When a model like OpenAI's o4-mini responds, it may emit a sequence of reasoning tokens and tool calls before producing its final text answer. By default, these parts render individually. ChainOfThoughtPrimitive groups consecutive reasoning + tool-call parts together and renders them through a single component.

Key benefits:

  • Cleaner UI — Collapse intermediate steps behind a "Thinking" toggle
  • Better context — Users see that reasoning and tool calls are related
  • Built-in accordion — Expand/collapse with a single click; collapsed by default

Quick Start#

Pass a ChainOfThought component to MessagePrimitive.Parts#

MessagePrimitive.Parts accepts a ChainOfThought component. When provided, consecutive reasoning and tool-call parts are automatically grouped and rendered through it.

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

const Reasoning: FC<{ text: string }> = ({ text }) => {
  return (
    <p className="whitespace-pre-wrap px-4 py-2 text-muted-foreground text-sm italic">
      {text}
    </p>
  );
};

const ChainOfThought: FC = () => {
  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>
          {({ part }) => {
            if (part.type === "reasoning") return <Reasoning {...part} />;
            if (part.type === "tool-call") return <ToolFallback {...part} />;
            return null;
          }}
        </ChainOfThoughtPrimitive.Parts>
      </AuiIf>
    </ChainOfThoughtPrimitive.Root>
  );
};

const AssistantMessage: FC = () => {
  return (
    <MessagePrimitive.Root>
      <MessagePrimitive.Parts>
        {({ part }) => {
          if (part.type === "text") return <MarkdownText />;
          return null;
        }}
      </MessagePrimitive.Parts>
    </MessagePrimitive.Root>
  );
};

Use a reasoning model#

Chain of Thought is most useful with models that produce reasoning tokens (e.g. OpenAI o4-mini). Here's an example backend route using the Vercel AI SDK:

import { openai } from "@ai-sdk/openai";
import { streamText, convertToModelMessages } from "ai";

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai("o4-mini"),
    messages: await convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}

API Reference#

ChainOfThoughtPrimitive.Root#

Container element for the chain of thought group. Renders a <div>.

ChainOfThoughtPrimitive.AccordionTrigger#

A button that toggles the collapsed/expanded state. Collapsed by default.

ChainOfThoughtPrimitive.Parts#

Renders the grouped parts when expanded (nothing when collapsed).

<AuiIf condition={(s) => !s.chainOfThought.collapsed}>
  <ChainOfThoughtPrimitive.Parts>
    {({ part }) => {
      if (part.type === "reasoning") return <Reasoning {...part} />;
      if (part.type === "tool-call") return <ToolFallback {...part} />;
      return null;
    }}
  </ChainOfThoughtPrimitive.Parts>
</AuiIf>
PropTypeDescription
components.ReasoningFC<{ text: string }>Component to render reasoning parts
components.tools.FallbackToolCallMessagePartComponentFallback component for tool-call parts
components.LayoutComponentType<PropsWithChildren>Wrapper component around each rendered part when expanded

Reading Collapsed State#

Use AuiIf to conditionally render based on the accordion state:

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

const ChainOfThoughtAccordionTrigger = () => {
  return (
    <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>
  );
};

Full Example#

See the complete with-chain-of-thought example for a working implementation with tool calls and reasoning.