Expert guidance for Vercel's Streamdown library - a streaming-optimized react-markdown replacement for AI applications. Use when: (1) Rendering AI-generated markdown from useChat/streamText, (2) Building chat UIs with streaming responses, (3) Migrating from react-markdown to streaming-friendly rendering, (4) Configuring code blocks (Shiki), math (KaTeX), diagrams (Mermaid), (5) Handling incomplete markdown during AI streaming (remend preprocessor), (6) Customizing markdown styling with Tailwind/CSS variables, (7) Securing AI output with rehype-harden (link/image protocols). Triggers: Streamdown, streaming markdown, AI chat markdown, react-markdown replacement, AI Elements Response, incomplete markdown, remend, Shiki themes, Mermaid diagrams, KaTeX math, rehype-harden, isAnimating, markdown streaming.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
name: streamdown description: | Expert guidance for Vercel's Streamdown library - a streaming-optimized react-markdown replacement for AI applications. Use when: (1) Rendering AI-generated markdown from useChat/streamText, (2) Building chat UIs with streaming responses, (3) Migrating from react-markdown to streaming-friendly rendering, (4) Configuring code blocks (Shiki), math (KaTeX), diagrams (Mermaid), (5) Handling incomplete markdown during AI streaming (remend preprocessor), (6) Customizing markdown styling with Tailwind/CSS variables, (7) Securing AI output with rehype-harden (link/image protocols). Triggers: Streamdown, streaming markdown, AI chat markdown, react-markdown replacement, AI Elements Response, incomplete markdown, remend, Shiki themes, Mermaid diagrams, KaTeX math, rehype-harden, isAnimating, markdown streaming.
Streamdown - AI Streaming Markdown
Streamdown is a drop-in react-markdown replacement designed for AI-powered streaming applications. It handles incomplete markdown syntax gracefully using the remend preprocessor.
Quick Start
Installation
# Direct installation
pnpm add streamdown
# Or via AI Elements CLI (includes Response component)
pnpm dlx ai-elements@latest add message
Tailwind Configuration
Tailwind v4 (globals.css):
@source "../node_modules/streamdown/dist/*.js";
Tailwind v3 (tailwind.config.js):
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./node_modules/streamdown/dist/*.js',
],
}
Basic Chat Example
'use client';
import { useChat } from '@ai-sdk/react';
import { Streamdown } from 'streamdown';
export default function Chat() {
const { messages, sendMessage, status } = useChat();
return (
<>
{messages.map(message => (
<div key={message.id}>
{message.parts
.filter(part => part.type === 'text')
.map((part, index) => (
<Streamdown
key={index}
isAnimating={status === 'streaming'}
>
{part.text}
</Streamdown>
))}
</div>
))}
</>
);
}
Core Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | string | required | Markdown content to render |
isAnimating | boolean | false | Disables interactive controls during streaming |
mode | "streaming" | "static" | "streaming" | Rendering mode |
shikiTheme | [BundledTheme, BundledTheme] | ['github-light', 'github-dark'] | Light/dark syntax themes |
controls | ControlsConfig | boolean | true | Button visibility for code/table/mermaid |
mermaid | MermaidOptions | {} | Diagram configuration |
components | object | {} | Custom element overrides |
className | string | "" | Container CSS class |
remarkPlugins | Pluggable[] | GFM, math, CJK | Markdown preprocessing |
rehypePlugins | Pluggable[] | raw, katex, harden | HTML processing |
parseIncompleteMarkdown | boolean | true | Enable remend preprocessor |
AI SDK Integration
Status-Based isAnimating
The status from useChat maps directly to Streamdown's isAnimating:
const { messages, status } = useChat();
// status: 'submitted' | 'streaming' | 'ready' | 'error'
<Streamdown isAnimating={status === 'streaming'}>
{content}
</Streamdown>
Message Parts Pattern
AI SDK v6 uses message parts instead of content string:
{messages.map(message => (
<div key={message.id}>
{message.parts
.filter(part => part.type === 'text')
.map((part, index) => (
<Streamdown key={index} isAnimating={status === 'streaming'}>
{part.text}
</Streamdown>
))}
</div>
))}
Memoized Response Component
Wrap Streamdown with React.memo for performance:
import { memo, ComponentProps } from 'react';
import { Streamdown } from 'streamdown';
export const Response = memo(
({ className, ...props }: ComponentProps<typeof Streamdown>) => (
<Streamdown
className={cn('prose dark:prose-invert max-w-none', className)}
{...props}
/>
)
);
Configuration Examples
Shiki Themes
import type { BundledTheme } from 'shiki';
const themes: [BundledTheme, BundledTheme] = ['github-light', 'github-dark'];
<Streamdown shikiTheme={themes}>{content}</Streamdown>
Controls
<Streamdown
controls={{
code: true, // Copy button on code blocks
table: true, // Download button on tables
mermaid: {
copy: true, // Copy diagram source
download: true, // Download as SVG
fullscreen: true, // Fullscreen view
panZoom: true, // Pan/zoom controls
},
}}
>
{content}
</Streamdown>
Mermaid Diagrams
import type { MermaidConfig } from 'streamdown';
const mermaidConfig: MermaidConfig = {
theme: 'base',
themeVariables: {
fontFamily: 'Inter, sans-serif',
primaryColor: 'hsl(var(--primary))',
lineColor: 'hsl(var(--border))',
},
};
<Streamdown mermaid={{ config: mermaidConfig }}>{content}</Streamdown>
Custom Error Component for Mermaid
import type { MermaidErrorComponentProps } from 'streamdown';
const MermaidError = ({ error, chart, retry }: MermaidErrorComponentProps) => (
<div className="p-4 border border-destructive rounded">
<p>Failed to render diagram</p>
<button onClick={retry}>Retry</button>
</div>
);
<Streamdown mermaid={{ errorComponent: MermaidError }}>{content}</Streamdown>
Custom Components
Override any markdown element:
<Streamdown
components={{
h1: ({ children }) => <h1 className="text-4xl font-bold">{children}</h1>,
a: ({ href, children }) => (
<a href={href} className="text-primary underline">{children}</a>
),
code: ({ children, className }) => (
<code className={cn('bg-muted px-1 rounded', className)}>{children}</code>
),
}}
>
{content}
</Streamdown>
Security Configuration
Restrict protocols for AI-generated content:
import { defaultRehypePlugins } from 'streamdown';
import { harden } from 'rehype-harden';
<Streamdown
rehypePlugins={[
defaultRehypePlugins.raw,
defaultRehypePlugins.katex,
[harden, {
allowedProtocols: ['http', 'https', 'mailto'],
allowedLinkPrefixes: ['https://your-domain.com'],
allowDataImages: false,
}],
]}
>
{content}
</Streamdown>
Streaming vs Static Mode
| Mode | Use Case | Features |
|---|---|---|
streaming | AI chat responses | Block parsing, incomplete markdown handling, memoization |
static | Blog posts, docs | Simpler rendering, no streaming optimizations |
// Static mode for pre-rendered content
<Streamdown mode="static">{blogContent}</Streamdown>
Built-in Features
- GFM: Tables, task lists, strikethrough, autolinks
- Math: KaTeX rendering with
$$...$$syntax - Code: Shiki syntax highlighting (200+ languages)
- Diagrams: Mermaid with interactive controls
- CJK: Proper emphasis handling for Chinese/Japanese/Korean
- Security: rehype-harden for link/image protocol restrictions
Reference Files
| Reference | Topics |
|---|---|
| api-reference.md | Complete props, types, plugins, data attributes |
| ai-sdk-integration.md | useChat patterns, server setup, message parts |
| styling-security.md | Tailwind, CSS variables, custom components, rehype-harden |
Common Patterns
Next.js Configuration
If you see bundling errors with Mermaid:
// next.config.js
module.exports = {
serverComponentsExternalPackages: ['langium', '@mermaid-js/parser'],
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.alias = {
...config.resolve.alias,
'vscode-jsonrpc': false,
'langium': false,
};
}
return config;
},
};
Shiki External Package
// next.config.js
{
transpilePackages: ['shiki'],
}
Version Notes
- Streamdown: Works with React 18+ (optimized for React 19)
- AI SDK: Designed for v6 (status-based streaming state)
- Tailwind: Supports v3 and v4 configurations
More by BjornMelin
View allWorld-class Vitest QA/test engineer for TypeScript + Next.js (local + CI performance focused)
Expert guidance for building AI agents with ToolLoopAgent (AI SDK v6+). Use when creating agents, configuring stopWhen/prepareStep, callOptionsSchema/prepareCall, dynamic tool selection, tool loops, or agent workflows (sequential, routing, evaluator-optimizer, orchestrator-worker). Triggers: ToolLoopAgent, agent loop, stopWhen, stepCountIs, prepareStep, callOptionsSchema, prepareCall, hasToolCall, InferAgentUIMessage, agent workflows.
Expert guidance for Zod v4 schema validation in TypeScript. Use when designing schemas, migrating from Zod 3, handling validation errors, generating JSON Schema/OpenAPI, using codecs/transforms, or integrating with React Hook Form, tRPC, Hono, or Next.js. Covers all Zod v4 APIs including top-level string formats, strictObject/looseObject, metadata, registries, branded types, and recursive schemas.
Expert guidance for building Dash applications with Dash Mantine Components (DMC) v2.x. Use when creating dashboards, forms, data visualization apps with DMC. Covers: MantineProvider theming, style props (m, p, c, bg, w, h), Styles API, callbacks (basic, pattern-matching ALL/MATCH/ALLSMALLER, clientside, background), multi-page apps with Dash Pages, charts (LineChart, BarChart, DonutChart), date pickers, modals, and all 100+ components. Triggers on: dash-mantine-components, DMC, MantineProvider, dmc.Button, dmc.Select, dmc.Modal, dmc.BarChart, Mantine theme, Dash UI components, Dash callbacks, multi-page Dash app, pattern-matching callbacks, clientside callbacks, AppShell.
