streamdown

Drop-in replacement for react-markdown optimized for AI-powered streaming. Handles incomplete Markdown gracefully during token-by-token generation.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "streamdown" with this command: npx skills add omerakben/omer-akben/omerakben-omer-akben-streamdown

Streamdown v2.x

Drop-in replacement for react-markdown optimized for AI-powered streaming. Handles incomplete Markdown gracefully during token-by-token generation.

Installation

pnpm add streamdown @streamdown/code @streamdown/mermaid @streamdown/math

Optional: @streamdown/cjk for CJK language support

Update globals.css for Tailwind:

@source "../node_modules/streamdown/dist/*.js";

Basic Usage

import { Streamdown } from "streamdown"; import { code } from "@streamdown/code"; import { mermaid } from "@streamdown/mermaid"; import { math } from "@streamdown/math";

<Streamdown plugins={{ code, mermaid, math }} isAnimating={isStreaming}

{markdownContent} </Streamdown>

Props Reference

Prop Type Default Description

children

string

Markdown content to render

isAnimating

boolean

false

Enable streaming mode (incomplete markdown handling)

plugins

PluginConfig

{}

Code, mermaid, math, cjk plugins

components

Components

{}

Custom React components for elements

controls

ControlsConfig

true

Show/hide interactive controls

mermaid

MermaidOptions

Mermaid diagram configuration

caret

"block" | "circle"

Streaming cursor style

className

string

Container CSS class

shikiTheme

[BundledTheme, BundledTheme]

["github-light", "github-dark"]

Shiki themes [light, dark]

linkSafety

LinkSafetyConfig

External link confirmation modal

remend

RemendOptions

Incomplete markdown parsing options

parseIncompleteMarkdown

boolean

true

Use remend for streaming

Plugins

Code Plugin (@streamdown/code)

Shiki-powered syntax highlighting with copy/download buttons.

import { code, createCodePlugin } from "@streamdown/code";

// Default plugins={{ code }}

// Custom themes const customCode = createCodePlugin({ themes: ["one-dark-pro", "one-light"] }); plugins={{ code: customCode }}

Math Plugin (@streamdown/math)

KaTeX rendering for LaTeX equations.

import { math, createMathPlugin } from "@streamdown/math"; import "katex/dist/katex.min.css"; // Required!

// Default plugins={{ math }}

// Enable single $ for inline math const customMath = createMathPlugin({ singleDollarTextMath: true, errorColor: "#ff0000" }); plugins={{ math: customMath }}

Syntax:

  • Inline: $E = mc^2$ or \(E = mc^2\)

  • Block: $$\sum_{i=1}^n x_i$$ or \[...\]

Mermaid Plugin (@streamdown/mermaid)

Interactive diagram rendering.

import { mermaid, createMermaidPlugin } from "@streamdown/mermaid";

plugins={{ mermaid }}

// With config prop <Streamdown plugins={{ mermaid }} mermaid={{ config: { theme: "base", themeVariables: { primaryColor: "#73000a", primaryTextColor: "#ffffff", } }, errorComponent: MyErrorComponent }}

CJK Plugin (@streamdown/cjk)

WARNING: This project disabled CJK due to English text corruption. Only use if CJK language support is required.

import { cjk } from "@streamdown/cjk"; plugins={{ cjk }}

Custom Components

Override default HTML element rendering:

const components: Components = { code({ className, children, ...props }) { const language = className?.match(/language-(\w+)/)?.[1]; if (language) { return <SyntaxHighlighter language={language}>{children}</SyntaxHighlighter>; } return <code className="inline-code" {...props}>{children}</code>; },

pre({ children }) { return <>{children}</>; // Let code handle wrapping },

a({ href, children, ...props }) { const isExternal = href?.startsWith("http"); return ( <a href={href} target={isExternal ? "_blank" : undefined} rel={isExternal ? "noopener noreferrer" : undefined} {...props} > {children} </a> ); },

table({ children }) { return ( <div className="overflow-x-auto"> <table className="min-w-full">{children}</table> </div> ); } };

<Streamdown components={components}>...</Streamdown>

Controls Configuration

// Enable all (default) controls={true}

// Disable all controls={false}

// Selective controls={{ table: true, // Table copy/download code: true, // Code copy/download mermaid: { download: true, copy: true, fullscreen: true, panZoom: true } }}

Link Safety

Confirmation modal for external links:

<Streamdown linkSafety={{ enabled: true, onLinkCheck: async (url) => { // Return true to auto-allow, false to show modal return url.startsWith("https://trusted-domain.com"); }, renderModal: ({ url, isOpen, onClose, onConfirm }) => ( <Dialog open={isOpen} onOpenChange={onClose}> <DialogContent> <p>Open external link: {url}?</p> <Button onClick={onConfirm}>Continue</Button> </DialogContent> </Dialog> ) }}

Streaming Best Practices

Error Boundary Pattern

Wrap Streamdown in an error boundary for graceful fallback:

class StreamdownErrorBoundary extends Component { state = { hasError: false };

static getDerivedStateFromError() { return { hasError: true }; }

render() { if (this.state.hasError) { return <pre className="whitespace-pre-wrap">{this.props.fallback}</pre>; } return this.props.children; } }

<StreamdownErrorBoundary fallback={rawContent}> <Streamdown isAnimating={isStreaming}>{content}</Streamdown> </StreamdownErrorBoundary>

Post-Stream Cleanup

Clean incomplete markers when streaming ends:

const processedContent = useMemo(() => { if (isStreaming || !content?.trim()) return content;

let cleaned = content; // Remove trailing incomplete bold/italic cleaned = cleaned.replace(/\s**{1,2}\s*$/, ""); // Balance orphaned ** const boldMatches = cleaned.match(/**/g); if (boldMatches && boldMatches.length % 2 !== 0) { cleaned = cleaned.replace(/**(\s*)$/, "$1"); } return cleaned; }, [content, isStreaming]);

Lazy Loading

Reduce initial bundle size:

const StreamdownContent = lazy(() => import("./streamdown-content").then((mod) => ({ default: mod.StreamdownContent, })) );

<Suspense fallback={<PlainTextFallback content={content} />}> <StreamdownContent content={content} isStreaming={isStreaming} /> </Suspense>

Common Issues

CJK Plugin Corrupts English Text

Symptom: Random Chinese characters like "落" appear in English text. Solution: Remove @streamdown/cjk plugin unless CJK support is required.

Mermaid Diagrams Crash

Symptom: Invalid mermaid syntax causes render error. Solution: Use error boundary with fallback + custom errorComponent prop.

Math Not Rendering

Symptom: Raw LaTeX syntax displayed. Solution: Ensure katex/dist/katex.min.css is imported.

Code Blocks Unstyled

Symptom: Code blocks have no syntax highlighting. Solution: Add @source directive in globals.css and use @streamdown/code plugin.

Streaming Markers Persist

Symptom: Trailing ** or * visible after streaming ends. Solution: Implement post-stream content cleanup (see pattern above).

Malformed Nested Bold

Symptom: Raw ** appears before keywords in bullet points, e.g., **The P-O-L-C Framework: . Cause: AI model tries to emphasize a keyword within already-bold text, creating invalid nested markdown. Solution: Add cleanup regex in post-stream processing:

// Fix malformed nested bold: "prefix keyword:" → "prefix keyword:" cleaned = cleaned.replace(/**([^*]+)\s**(\w+)**(:?)/g, "$1 $2**$3");

TypeScript Types

import type { StreamdownProps, Components, PluginConfig, ControlsConfig, MermaidOptions, LinkSafetyConfig, CodeHighlighterPlugin, DiagramPlugin, MathPlugin, CjkPlugin, } from "streamdown";

import type { BundledLanguage, BundledTheme } from "shiki";

Integration with AI SDK

import { useChat } from "@ai-sdk/react";

export function Chat() { const { messages, status } = useChat(); const isStreaming = status === "streaming";

return ( <div> {messages.map((message) => ( <div key={message.id}> {message.parts.map((part, i) => part.type === "text" ? ( <Streamdown key={i} plugins={{ code, mermaid, math }} isAnimating={isStreaming && i === message.parts.length - 1} > {part.text} </Streamdown> ) : null )} </div> ))} </div> ); }

For project-specific implementation patterns, see components/ui/streamdown-content.tsx .

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

bundle-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
General

web-artifacts-builder

No summary provided by upstream source.

Repository SourceNeeds Review
General

nextjs-page-creator

No summary provided by upstream source.

Repository SourceNeeds Review
General

doc-coauthoring

No summary provided by upstream source.

Repository SourceNeeds Review