Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Streamdown
https://github.com/vercel/streamdown
Admin
Streamdown is a drop-in replacement for react-markdown, specifically designed to handle AI-powered
...
Tokens:
63,303
Snippets:
736
Trust Score:
10
Update:
5 days ago
Context
Skills
Chat
Benchmark
83.7
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Streamdown Streamdown is a drop-in replacement for react-markdown designed specifically for AI-powered streaming applications. It provides real-time rendering of Markdown content as it streams from AI models, intelligently handling incomplete syntax through its integrated `remend` preprocessor that automatically completes unterminated bold, italic, code blocks, links, and other Markdown patterns. Built with React 18/19 and Tailwind CSS, it offers syntax highlighting via Shiki, Mermaid diagrams, KaTeX math rendering, and CJK language support through a modular plugin system. The library consists of a core `streamdown` package and optional plugins (`@streamdown/code`, `@streamdown/math`, `@streamdown/mermaid`, `@streamdown/cjk`) that can be installed separately. Streamdown includes built-in security features using `rehype-sanitize` and `rehype-harden` to protect against XSS attacks and malicious content, making it safe for rendering user-generated or AI-generated content. It supports both streaming mode for real-time AI responses and static mode for pre-rendered documentation or blog posts. ## Installation Install the core package and configure Tailwind CSS to include Streamdown's styles. ```bash npm install streamdown ``` ```css /* globals.css - Tailwind v4 */ @source "../node_modules/streamdown/dist/*.js"; ``` ```tsx // app/layout.tsx - Import animation styles if using animated prop import "streamdown/styles.css"; ``` ## Basic Usage Render Markdown content with the Streamdown component as a drop-in replacement for react-markdown. ```tsx import { Streamdown } from 'streamdown'; export default function Page() { const markdown = ` # Hello World This is **streaming** markdown with: - Lists - **Bold** and *italic* text - [Links](https://example.com) - \`inline code\` `; return <Streamdown>{markdown}</Streamdown>; } ``` ## AI Streaming Integration with Vercel AI SDK Integrate Streamdown with the Vercel AI SDK for real-time chat applications with streaming markdown rendering. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { Streamdown } from 'streamdown'; import { code } from '@streamdown/code'; import { mermaid } from '@streamdown/mermaid'; export default function ChatPage() { const { messages, input, handleInputChange, handleSubmit, status } = useChat(); return ( <div className="flex flex-col h-screen"> <div className="flex-1 overflow-y-auto p-4 space-y-4"> {messages.map((message) => ( <div key={message.id} className={message.role === 'user' ? 'text-right' : 'text-left'} > <div className="inline-block max-w-2xl"> <Streamdown plugins={{ code, mermaid }} isAnimating={status === 'streaming' && message.role === 'assistant'} > {message.content} </Streamdown> </div> </div> ))} </div> <form onSubmit={handleSubmit} className="p-4 border-t"> <input value={input} onChange={handleInputChange} placeholder="Ask me anything..." className="w-full px-4 py-2 border rounded-lg" disabled={status === 'streaming'} /> </form> </div> ); } ``` ## Syntax Highlighting with @streamdown/code Add syntax highlighting for 200+ programming languages with Shiki, featuring dual theme support for light/dark modes. ```bash npm install @streamdown/code ``` ```tsx import { Streamdown } from 'streamdown'; import { code } from '@streamdown/code'; export default function Page() { const markdown = ` \`\`\`typescript interface User { id: number; name: string; email: string; } async function fetchUser(id: number): Promise<User> { const response = await fetch(\`/api/users/\${id}\`); return response.json(); } \`\`\` `; return ( <Streamdown plugins={{ code }} shikiTheme={['github-light', 'github-dark']} lineNumbers={true} > {markdown} </Streamdown> ); } ``` ## Math Rendering with @streamdown/math Render mathematical expressions using KaTeX with support for inline and block math using double dollar signs. ```bash npm install @streamdown/math ``` ```tsx import { Streamdown } from 'streamdown'; import { math } from '@streamdown/math'; import 'katex/dist/katex.min.css'; export default function Page() { const markdown = ` The quadratic formula is $$x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$$ for solving equations. Block math for Euler's identity: $$ e^{i\\pi} + 1 = 0 $$ And the normal distribution: $$ f(x) = \\frac{1}{\\sigma\\sqrt{2\\pi}} e^{-\\frac{1}{2}\\left(\\frac{x-\\mu}{\\sigma}\\right)^2} $$ `; return ( <Streamdown plugins={{ math }}> {markdown} </Streamdown> ); } ``` ## Mermaid Diagrams with @streamdown/mermaid Render interactive Mermaid diagrams including flowcharts, sequence diagrams, state diagrams, and more. ```bash npm install @streamdown/mermaid ``` ```tsx import { Streamdown } from 'streamdown'; import { mermaid } from '@streamdown/mermaid'; export default function Page() { const markdown = ` \`\`\`mermaid sequenceDiagram participant User participant Browser participant Server participant Database User->>Browser: Enter URL Browser->>Server: HTTP Request Server->>Database: Query data Database-->>Server: Return results Server-->>Browser: HTTP Response Browser-->>User: Display page \`\`\` `; return ( <Streamdown plugins={{ mermaid }} mermaid={{ config: { theme: 'neutral', fontFamily: 'monospace', }, }} controls={{ mermaid: { fullscreen: true, download: true, copy: true, panZoom: true, }, }} > {markdown} </Streamdown> ); } ``` ## CJK Language Support with @streamdown/cjk Properly handle Chinese, Japanese, and Korean text with correct emphasis formatting near ideographic punctuation. ```bash npm install @streamdown/cjk ``` ```tsx import { Streamdown } from 'streamdown'; import { cjk } from '@streamdown/cjk'; export default function Page() { const markdown = ` **日本語の文章(括弧付き)。**この文が後に続いても大丈夫です。 **中文文本(带括号)。**这句子继续也没问题。 **한국어 구문(괄호 포함)**을 강조. `; return ( <Streamdown plugins={{ cjk }}> {markdown} </Streamdown> ); } ``` ## Full Plugin Configuration Combine all plugins for comprehensive markdown rendering with syntax highlighting, math, diagrams, and CJK support. ```tsx import { Streamdown } from 'streamdown'; import { code } from '@streamdown/code'; import { math } from '@streamdown/math'; import { mermaid } from '@streamdown/mermaid'; import { cjk } from '@streamdown/cjk'; import 'katex/dist/katex.min.css'; import 'streamdown/styles.css'; export default function Page() { const markdown = ` # Complete Example Here's code with syntax highlighting: \`\`\`typescript const greeting = "Hello, World!"; console.log(greeting); \`\`\` Math: $$E = mc^2$$ \`\`\`mermaid graph LR A[Start] --> B[End] \`\`\` `; return ( <Streamdown plugins={{ code, math, mermaid, cjk }} shikiTheme={['github-light', 'github-dark']} animated={{ animation: 'blurIn', duration: 200 }} isAnimating={false} lineNumbers={true} controls={{ code: { copy: true, download: true }, table: { copy: true, download: true, fullscreen: true }, mermaid: { fullscreen: true, download: true, copy: true }, }} > {markdown} </Streamdown> ); } ``` ## Custom Component Overrides Replace default markdown elements with custom React components while maintaining Streamdown functionality. ```tsx import { Streamdown } from 'streamdown'; export default function Page() { return ( <Streamdown components={{ h1: ({ children }) => ( <h1 className="text-4xl font-bold text-blue-600 mb-4">{children}</h1> ), h2: ({ children, className }) => ( <h2 className={`mt-6 mb-2 font-semibold text-2xl text-blue-500 ${className ?? ''}`}> {children} </h2> ), a: ({ href, children, ...props }) => ( <a href={href} className="text-purple-600 hover:text-purple-800 underline" target="_blank" rel="noreferrer" {...props} > {children} </a> ), inlineCode: ({ children }) => ( <code className="rounded bg-violet-100 px-1.5 py-0.5 text-violet-800 text-sm"> {children} </code> ), }} > {markdown} </Streamdown> ); } ``` ## Custom HTML Tags Render custom HTML tags from AI responses using allowedTags and components props. ```tsx import { Streamdown } from 'streamdown'; // AI outputs: <source id="123">Getting Started Guide</source> export default function Page() { const markdown = ` According to the documentation <source id="abc">Getting Started Guide</source>, you should... Hello <mention user_id="user_123">John Doe</mention>, welcome! `; return ( <Streamdown allowedTags={{ source: ['id'], mention: ['user_id', 'type'], }} literalTagContent={['mention']} components={{ source: ({ id, children }) => ( <button onClick={() => console.log(`Navigate to source: ${id}`)} className="text-blue-600 underline cursor-pointer" > {children} </button> ), mention: ({ user_id, children }) => ( <span className="bg-blue-100 text-blue-800 px-1 rounded"> @{children} </span> ), }} > {markdown} </Streamdown> ); } ``` ## Custom Renderers for Code Fences Register custom React components for arbitrary code fence languages like Vega-Lite charts or D2 diagrams. ```tsx import { Streamdown } from 'streamdown'; import type { CustomRendererProps } from 'streamdown'; import { CodeBlockContainer, CodeBlockHeader } from 'streamdown'; import { useEffect, useRef } from 'react'; const VegaLiteRenderer = ({ code, language, isIncomplete }: CustomRendererProps) => { const containerRef = useRef<HTMLDivElement>(null); useEffect(() => { if (isIncomplete || !containerRef.current) return; let cancelled = false; const render = async () => { const spec = JSON.parse(code); const vegaEmbed = (await import('vega-embed')).default; if (cancelled || !containerRef.current) return; containerRef.current.innerHTML = ''; await vegaEmbed(containerRef.current, spec, { actions: false, renderer: 'svg' }); }; render(); return () => { cancelled = true; }; }, [code, isIncomplete]); return ( <CodeBlockContainer isIncomplete={isIncomplete} language={language}> <CodeBlockHeader language={language} /> {isIncomplete ? ( <div className="flex h-48 items-center justify-center bg-muted">Loading chart...</div> ) : ( <div ref={containerRef} className="overflow-hidden rounded-md p-4" /> )} </CodeBlockContainer> ); }; export default function Page() { const markdown = ` \`\`\`vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "width": "container", "height": 200, "data": { "values": [{"month": "Jan", "revenue": 28}, {"month": "Feb", "revenue": 55}] }, "mark": "bar", "encoding": { "x": {"field": "month"}, "y": {"field": "revenue"} } } \`\`\` `; return ( <Streamdown plugins={{ renderers: [{ language: ['vega', 'vega-lite'], component: VegaLiteRenderer }], }} > {markdown} </Streamdown> ); } ``` ## Streaming Animation Enable per-word text animation for smooth streaming effects with built-in fadeIn, blurIn, and slideUp animations. ```tsx import { Streamdown } from 'streamdown'; import 'streamdown/styles.css'; export default function Page({ status, markdown }) { return ( <Streamdown animated={{ animation: 'blurIn', // 'fadeIn' | 'blurIn' | 'slideUp' duration: 200, // milliseconds easing: 'ease-out', // CSS timing function sep: 'word', // 'word' | 'char' }} isAnimating={status === 'streaming'} onAnimationStart={() => console.log('Streaming started')} onAnimationEnd={() => console.log('Streaming ended')} > {markdown} </Streamdown> ); } ``` ## Remend: Self-Healing Markdown Use remend standalone or configure it within Streamdown to handle incomplete markdown during streaming. ```tsx // Standalone usage import remend from 'remend'; const partialMarkdown = "This is **incomplete bold"; const completed = remend(partialMarkdown); // Result: "This is **incomplete bold**" // Configure incomplete link handling remend("[Click here](http://exampl", { linkMode: 'text-only' }); // Result: "Click here" ``` ```tsx // Within Streamdown import { Streamdown } from 'streamdown'; export default function Page() { return ( <Streamdown parseIncompleteMarkdown={true} remend={{ links: true, images: true, bold: true, italic: true, inlineCode: true, strikethrough: true, katex: true, linkMode: 'protocol', // 'protocol' | 'text-only' }} > {markdown} </Streamdown> ); } ``` ## Custom Remend Handlers Extend remend with custom handlers to complete domain-specific syntax patterns. ```tsx import remend, { type RemendHandler } from 'remend'; const jokeHandler: RemendHandler = { name: 'joke', handle: (text) => { const match = text.match(/<<<JOKE>>>([^<]*)$/); if (match && !text.endsWith('<<</JOKE>>>')) { return `${text}<<</JOKE>>>`; } return text; }, priority: 80, // Built-ins use 0-75, default is 100 }; const result = remend(content, { handlers: [jokeHandler] }); ``` ## Security Configuration Configure URL restrictions and content sanitization for untrusted or AI-generated content. ```tsx import { Streamdown, defaultRehypePlugins } from 'streamdown'; import { harden } from 'rehype-harden'; export default function ChatMessage({ content, isAIGenerated }) { const securityConfig = isAIGenerated ? { defaultOrigin: 'https://your-app.com', allowedLinkPrefixes: [ 'https://your-app.com', 'https://docs.your-app.com', 'https://github.com', ], allowedImagePrefixes: ['https://your-cdn.com'], allowedProtocols: ['http', 'https', 'mailto'], allowDataImages: false, } : { allowedLinkPrefixes: ['*'], allowedImagePrefixes: ['*'], allowedProtocols: ['*'], }; return ( <Streamdown rehypePlugins={[ defaultRehypePlugins.raw, defaultRehypePlugins.sanitize, [harden, securityConfig], ]} > {content} </Streamdown> ); } ``` ## Static Mode for Documentation Use static mode for pre-rendered content like blog posts or documentation where streaming optimizations are unnecessary. ```tsx import { Streamdown } from 'streamdown'; import { code } from '@streamdown/code'; import { mermaid } from '@streamdown/mermaid'; export default function BlogPost({ content }: { content: string }) { return ( <Streamdown mode="static" plugins={{ code, mermaid }} shikiTheme={['github-light', 'github-dark']} > {content} </Streamdown> ); } ``` ## Streaming State Detection Hook Use the useIsCodeFenceIncomplete hook to detect when code blocks are still streaming for custom renderers. ```tsx import { Streamdown, useIsCodeFenceIncomplete } from 'streamdown'; const MyCodeBlock = ({ children }) => { const isIncomplete = useIsCodeFenceIncomplete(); if (isIncomplete) { return <div className="animate-pulse bg-muted h-24 rounded" />; } return <pre><code>{children}</code></pre>; }; export default function Page({ isStreaming, markdown }) { return ( <Streamdown components={{ code: MyCodeBlock }} isAnimating={isStreaming} > {markdown} </Streamdown> ); } ``` ## Table Interactivity Tables include built-in copy, download, and fullscreen controls. Access table utilities for custom implementations. ```tsx import { Streamdown, TableCopyDropdown, TableDownloadDropdown, TableDownloadButton, extractTableDataFromElement, tableDataToCSV, tableDataToTSV, tableDataToMarkdown, } from 'streamdown'; // Custom table component with controls const CustomTable = ({ children, className }) => ( <div data-streamdown="table-wrapper"> <div className="flex items-center justify-end gap-1"> <TableCopyDropdown /> <TableDownloadDropdown /> </div> <table className={className}>{children}</table> </div> ); // Or use utilities directly const handleExport = (tableElement: HTMLTableElement) => { const data = extractTableDataFromElement(tableElement); const csv = tableDataToCSV(data); const tsv = tableDataToTSV(data); const markdown = tableDataToMarkdown(data); }; ``` ## Element Filtering Filter allowed/disallowed elements and transform URLs using react-markdown compatible APIs. ```tsx import { Streamdown, defaultUrlTransform } from 'streamdown'; export default function Page() { return ( <Streamdown // Only allow specific elements allowedElements={['p', 'a', 'em', 'strong', 'code']} // Or disallow specific elements // disallowedElements={['img', 'script']} // Replace disallowed elements with their children unwrapDisallowed={true} // Custom element filter allowElement={(element) => !['h3', 'h4', 'h5', 'h6'].includes(element.tagName)} // Transform URLs (e.g., proxy images through CDN) urlTransform={(url, key, node) => { if (key === 'src') { return `https://your-cdn.com/proxy?url=${encodeURIComponent(url)}`; } return defaultUrlTransform(url, key, node); }} // Skip raw HTML entirely skipHtml={false} > {markdown} </Streamdown> ); } ``` ## Summary Streamdown is ideal for building AI-powered chat interfaces, documentation sites, and any application that needs real-time markdown rendering. Its primary use cases include: streaming AI responses with proper handling of incomplete syntax, rendering rich content with code highlighting and diagrams, building chat applications with the Vercel AI SDK, and creating static documentation with the same component. The modular plugin architecture allows you to include only the features you need, keeping bundle sizes minimal. Integration patterns typically involve combining Streamdown with AI SDK providers (Vercel AI SDK, LangChain, etc.) for chat applications, using static mode for pre-rendered content like blogs or docs, and configuring security settings based on content trust levels. The library's drop-in compatibility with react-markdown makes migration straightforward, while its streaming-optimized architecture and remend preprocessor provide significant improvements for real-time AI content rendering.