skills/streamdown/SKILL.md
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.
npx skillsauth add bjornmelin/dev-skills streamdownInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Streamdown is a drop-in react-markdown replacement designed for AI-powered streaming applications. It handles incomplete markdown syntax gracefully using the remend preprocessor.
# Direct installation
pnpm add streamdown
# Or via AI Elements CLI (includes Response component)
pnpm dlx ai-elements@latest add message
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',
],
}
'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>
))}
</>
);
}
| 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 |
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>
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>
))}
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}
/>
)
);
import type { BundledTheme } from 'shiki';
const themes: [BundledTheme, BundledTheme] = ['github-light', 'github-dark'];
<Streamdown shikiTheme={themes}>{content}</Streamdown>
<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>
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>
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>
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>
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>
| 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>
Run the local scanner before Streamdown migrations or chat-markdown reviews:
python3 skills/streamdown/scripts/ai_stack_scan.py --root <repo> --pretty
It emits ai_stack_scan.v1, uses no network by default, skips symlinks, and
flags likely migration gaps such as react-markdown in AI streaming surfaces,
Streamdown usage without isAnimating, missing streamdown dependencies, and
missing Tailwind streamdown/dist source/content configuration in config or
CSS. Verify signals against current Streamdown docs/source before editing. Keep
full scanner JSON local; share only specific redacted signals externally.
$$...$$ syntax| 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 |
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;
},
};
// next.config.js
{
transpilePackages: ['shiki'],
}
development
Repo/monorepo modernization: dependency upgrades, security fixes, deprecation cleanup, framework migrations, dependency-native refactors, and verified hard-cut simplification.
development
Use this skill for Browser Web Animations API: Element.animate(), Animation, KeyframeEffect, playback control, generated keyframes, cancel/finish, commitStyles, and cleanup. Trigger on Element.animate, WAAPI, Web Animations API, KeyframeEffect, Animation object, commitStyles. Do not use for near-miss tasks outside these boundaries; route to adjacent motion or platform skills when they own the implementation.
tools
Use this skill for Three.js, React Three Fiber, Drei, Canvas/createRoot lifecycle, loaders, GLTF, useFrame, disposal, SSR/client boundaries, DPR, and browser proof. Trigger on Three.js, THREE, @react-three/fiber, @react-three/drei, R3F Canvas, useFrame, GLTF, WebGLRenderer. Do not use for near-miss tasks outside these boundaries; route to adjacent motion or platform skills when they own the implementation.
development
Use this skill for Tailwind CSS v4 transition, animation, duration, easing, motion-safe/motion-reduce, @theme motion tokens, and static class safety. Trigger on Tailwind animation, transition-all, motion-safe, motion-reduce, @theme, animate-, duration-. Do not use for near-miss tasks outside these boundaries; route to adjacent motion or platform skills when they own the implementation.