Reasoning

PreviousNext

Display AI reasoning process with expandable content and duration tracking.

"use client"

import { useCallback, useEffect, useState } from "react"

import {
  Reasoning,
  ReasoningContent,
  ReasoningTrigger,
} from "@/components/ai-elements/reasoning"

const reasoningSteps = [
  "Let me think about this problem step by step.",
  "\n\nFirst, I need to understand what the user is asking for.",
  "\n\nThey want a reasoning component that opens automatically when streaming begins and closes when streaming finishes. The component should be composable and follow existing patterns in the codebase.",
  "\n\nThis seems like a collapsible component with state management would be the right approach.",
].join("")

export function ReasoningDemo() {
  const [content, setContent] = useState("")
  const [isStreaming, setIsStreaming] = useState(false)
  const [currentTokenIndex, setCurrentTokenIndex] = useState(0)
  const [tokens, setTokens] = useState<string[]>([])

  // Function to chunk text into fake tokens of 3-4 characters
  const chunkIntoTokens = useCallback((text: string): string[] => {
    const tokens: string[] = []
    let i = 0
    while (i < text.length) {
      const chunkSize = Math.floor(Math.random() * 2) + 3 // Random size between 3-4
      tokens.push(text.slice(i, i + chunkSize))
      i += chunkSize
    }
    return tokens
  }, [])

  useEffect(() => {
    const tokenizedSteps = chunkIntoTokens(reasoningSteps)
    setTokens(tokenizedSteps)
    setContent("")
    setCurrentTokenIndex(0)
    setIsStreaming(true)
  }, [chunkIntoTokens])

  useEffect(() => {
    if (!isStreaming || currentTokenIndex >= tokens.length) {
      if (isStreaming) {
        setIsStreaming(false)
      }
      return
    }

    const timer = setTimeout(() => {
      setContent((prev) => prev + tokens[currentTokenIndex])
      setCurrentTokenIndex((prev) => prev + 1)
    }, 25) // Faster interval since we're streaming smaller chunks

    return () => clearTimeout(timer)
  }, [isStreaming, currentTokenIndex, tokens])

  return (
    <div className="w-full p-4" style={{ height: "300px" }}>
      <Reasoning className="w-full" isStreaming={isStreaming}>
        <ReasoningTrigger />
        <ReasoningContent>{content}</ReasoningContent>
      </Reasoning>
    </div>
  )
}

Installation

pnpm dlx shadcn@latest add reasoning

Usage

import {
  Reasoning,
  ReasoningTrigger,
  ReasoningContent,
} from "@/components/ai-elements/reasoning"
<Reasoning isStreaming={false} duration={3}>
  <ReasoningTrigger />
  <ReasoningContent>
    {reasoningText}
  </ReasoningContent>
</Reasoning>

Examples

Default

"use client"

import { useCallback, useEffect, useState } from "react"

import {
  Reasoning,
  ReasoningContent,
  ReasoningTrigger,
} from "@/components/ai-elements/reasoning"

const reasoningSteps = [
  "Let me think about this problem step by step.",
  "\n\nFirst, I need to understand what the user is asking for.",
  "\n\nThey want a reasoning component that opens automatically when streaming begins and closes when streaming finishes. The component should be composable and follow existing patterns in the codebase.",
  "\n\nThis seems like a collapsible component with state management would be the right approach.",
].join("")

export function ReasoningDemo() {
  const [content, setContent] = useState("")
  const [isStreaming, setIsStreaming] = useState(false)
  const [currentTokenIndex, setCurrentTokenIndex] = useState(0)
  const [tokens, setTokens] = useState<string[]>([])

  // Function to chunk text into fake tokens of 3-4 characters
  const chunkIntoTokens = useCallback((text: string): string[] => {
    const tokens: string[] = []
    let i = 0
    while (i < text.length) {
      const chunkSize = Math.floor(Math.random() * 2) + 3 // Random size between 3-4
      tokens.push(text.slice(i, i + chunkSize))
      i += chunkSize
    }
    return tokens
  }, [])

  useEffect(() => {
    const tokenizedSteps = chunkIntoTokens(reasoningSteps)
    setTokens(tokenizedSteps)
    setContent("")
    setCurrentTokenIndex(0)
    setIsStreaming(true)
  }, [chunkIntoTokens])

  useEffect(() => {
    if (!isStreaming || currentTokenIndex >= tokens.length) {
      if (isStreaming) {
        setIsStreaming(false)
      }
      return
    }

    const timer = setTimeout(() => {
      setContent((prev) => prev + tokens[currentTokenIndex])
      setCurrentTokenIndex((prev) => prev + 1)
    }, 25) // Faster interval since we're streaming smaller chunks

    return () => clearTimeout(timer)
  }, [isStreaming, currentTokenIndex, tokens])

  return (
    <div className="w-full p-4" style={{ height: "300px" }}>
      <Reasoning className="w-full" isStreaming={isStreaming}>
        <ReasoningTrigger />
        <ReasoningContent>{content}</ReasoningContent>
      </Reasoning>
    </div>
  )
}
const reasoningText = `To solve this problem, I need to:
 
1. Understand the user's question
2. Consider the best approaches
3. Think about edge cases
4. Provide a clear solution
 
Based on this analysis, I recommend...`
 
export default function Example() {
  return (
    <Reasoning isStreaming={false} duration={3} defaultOpen={false}>
      <ReasoningTrigger />
      <ReasoningContent>{reasoningText}</ReasoningContent>
    </Reasoning>
  )
}

Streaming

const [isStreaming, setIsStreaming] = useState(true)
const [content, setContent] = useState("")
 
// Simulate streaming
useEffect(() => {
  // ... streaming logic
}, [])
 
<Reasoning isStreaming={isStreaming}>
  <ReasoningTrigger />
  <ReasoningContent>{content}</ReasoningContent>
</Reasoning>

Features

  • Duration Tracking: Automatically tracks and displays thinking time
  • Auto-expand/collapse: Opens during streaming, closes after completion
  • Streaming Support: Shows animated loading state while streaming
  • Formatted Content: Renders markdown content

API Reference

Reasoning

Main reasoning container.

PropTypeDefaultDescription
isStreamingbooleanfalseWhether content is currently streaming
durationnumber-Thinking duration in seconds
openboolean-Controlled open state
defaultOpenbooleantrueInitial open state
onOpenChange(open: boolean) => void-Callback when open state changes
classNamestring-Custom CSS classes

ReasoningTrigger

Button to toggle reasoning visibility.

PropTypeDefaultDescription
childrenReactNode-Custom trigger content
classNamestring-Custom CSS classes

ReasoningContent

Container for reasoning text.

PropTypeDefaultDescription
childrenstring-Reasoning text content
classNamestring-Custom CSS classes