Streaming

Streaming components handle real-time AI responses with smooth visual transitions. They work with any streaming source — the AI SDK, Server-Sent Events, or WebSockets.

Overview

When an AI model generates a response, tokens arrive one at a time. Streaming components buffer these tokens and render them progressively, providing visual feedback that feels natural and responsive.

Streaming text

The simplest streaming pattern uses the AI SDK's useChat hook with Streamdown:

components/chat-message.tsx
"use client"

import { useChat } from "@ai-sdk/react"
import { Streamdown } from "streamdown"
import { code } from "@streamdown/code"
import { math } from "@streamdown/math"

export function ChatInterface() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } =
    useChat()

  return (
    <div>
      {messages.map((message) => (
        <div key={message.id}>
          {message.role === "assistant" ? (
            <Streamdown
              plugins={{ code, math }}
              animated={isLoading && message === messages.at(-1)}
            >
              {message.content}
            </Streamdown>
          ) : (
            <p>{message.content}</p>
          )}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Ask a question..."
        />
      </form>
    </div>
  )
}

With Streamdown

Streamdown is purpose-built for streaming. Its animated prop controls whether new content animates in or appears instantly:

tsx
// Animated during streaming
<Streamdown animated={isStreaming} plugins={{ code, math }}>
  {partialContent}
</Streamdown>

// Static after complete
<Streamdown animated={false} plugins={{ code, math }}>
  {completeContent}
</Streamdown>

Handles unterminated syntax

Streamdown gracefully handles unterminated code blocks, incomplete markdown syntax, and partial LaTeX expressions during streaming.

Loading states

Provide visual feedback while waiting for the first token:

components/typing-indicator.tsx
export function TypingIndicator() {
  return (
    <div className="flex items-center gap-1 px-3 py-2">
      <div className="h-2 w-2 rounded-full bg-muted-foreground/40 animate-bounce" />
      <div className="h-2 w-2 rounded-full bg-muted-foreground/40 animate-bounce [animation-delay:150ms]" />
      <div className="h-2 w-2 rounded-full bg-muted-foreground/40 animate-bounce [animation-delay:300ms]" />
    </div>
  )
}