Reasoning

Collapsible reasoning UI for assistant "thinking" traces with stream duration tracking and markdown streaming support.

The Reasoning component is a collapsible interface designed to show assistant thinking/reasoning traces (e.g. from models like DeepSeek-R1 or Claude 3.7 Sonnet). It automatically handles stream duration timing, auto-opens while streaming, auto-closes when done (configurable), and renders streaming markdown gracefully using Streamdown.

Preview

Usage

Installation

$ pnpm dlx shadcn add https://infinityui.vercel.app/r/reasoning.json

Manual Installation

Install the required dependencies:

pnpm add @radix-ui/react-collapsible streamdown tw-shimmer

Import the component code into your project: Copy and paste reasoning.tsx into your components directory (e.g. components/infinity-ui/reasoning.tsx).

Basic Example

import {
  Reasoning,
  ReasoningTrigger,
  ReasoningContent,
} from "@/components/infinity-ui/reasoning";

export default function App() {
  const isReasoningStreaming = true;
  const reasoningMarkdown = "Analyzing the dataset... \n- Check column names\n- Validate types";

  return (
    <Reasoning isStreaming={isReasoningStreaming}>
      <ReasoningTrigger />
      <ReasoningContent>{reasoningMarkdown}</ReasoningContent>
    </Reasoning>
  );
}

Vercel AI SDK Integration

Some models emit structured reasoning parts in the AI SDK response stream. In practice, models like DeepSeek R1 and Claude can return reasoning content that you can render with Reasoning.

Use Reasoning with Vercel AI SDK by mapping assistant reasoning parts:

Install the AI SDK:

pnpm add ai @ai-sdk/react

Create your chat API route (e.g., enable Anthropic thinking so reasoning parts are returned):

// app/api/chat/route.ts
import { streamText, smoothStream, UIMessage, convertToModelMessages } from "ai";

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

  const result = streamText({
    model: "anthropic/claude-sonnet-4.5",
    messages: await convertToModelMessages(messages),
    experimental_transform: smoothStream({
      chunking: "word",
      delayInMs: 18,
    }),
    providerOptions: {
      anthropic: {
        thinking: { type: "enabled", budgetTokens: 1024 },
      },
    },
  });

  return result.toUIMessageStreamResponse({
    sendReasoning: true,
  });
}

Render assistant reasoning parts in your chat interface:

"use client";

import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport, isReasoningUIPart, type UIMessage } from "ai";
import {
  Reasoning,
  ReasoningContent,
  ReasoningTrigger,
} from "@/components/infinity-ui/reasoning";

function reasoningPartsFromMessage(message: UIMessage) {
  return message.parts.filter(isReasoningUIPart);
}

function reasoningTextFromMessage(message: UIMessage) {
  return reasoningPartsFromMessage(message).map((p) => p.text).join("");
}

function reasoningStreamingFromMessage(message: UIMessage) {
  const parts = reasoningPartsFromMessage(message);
  return parts.some((p) => p.state === "streaming");
}

export default function ChatView() {
  const { messages } = useChat({
    transport: new DefaultChatTransport({ api: "/api/chat" }),
  });

  const assistant = [...messages].reverse().find((m) => m.role === "assistant");
  if (!assistant) return null;

  const reasoningText = reasoningTextFromMessage(assistant);
  const isReasoningStreaming = reasoningStreamingFromMessage(assistant);

  if (!reasoningText.trim()) return null;

  return (
    <Reasoning isStreaming={isReasoningStreaming}>
      <ReasoningTrigger />
      <ReasoningContent>{reasoningText}</ReasoningContent>
    </Reasoning>
  );
}

API Reference

Reasoning

Root component. Manages streaming timing, open state behavior, and label context. Wraps @radix-ui/react-collapsible's Root primitive.

PropTypeDefaultDescription
isStreamingbooleanfalseControls reasoning stream lifecycle. Drives timer, auto-open/close states, and trigger label text updates.
openbooleanControlled open state of the collapsible content.
defaultOpenbooleanfalseInitial open state in uncontrolled mode.
onOpenChange(open: boolean) => voidCallback fired when the open state changes (both manual toggle and stream-driven auto-expand/collapse).

ReasoningTrigger

Trigger row with a built-in Brain icon and rotating indicator chevron. Wraps @radix-ui/react-collapsible's Trigger primitive.

PropTypeDefaultDescription
childrenReactNodeOptional custom label override for the trigger button. When omitted, uses the contextual label (e.g. Thinking... or Thought for N seconds).

ReasoningContent

Collapsible content container that handles smooth expansion animations and renders markdown stream tokens. Wraps @radix-ui/react-collapsible's Content primitive.

PropTypeDefaultDescription
childrenstringMarkdown text to render. This component expects a raw string and streams it through Streamdown.