@assistant-ui/react-streamdown
Streamdown-based markdown rendering for assistant-ui. An alternative to @assistant-ui/react-markdown with built-in syntax highlighting, math, and diagram support.
When to Use
@assistant-ui/react-markdown | Lightweight, bring-your-own syntax highlighter |
@assistant-ui/react-streamdown | Feature-rich with built-in Shiki, KaTeX, Mermaid |
Installation
npm install @assistant-ui/react-streamdown streamdown
For additional features, install the optional plugins:
npm install @streamdown/code @streamdown/math @streamdown/mermaid @streamdown/cjk
Usage
Basic
import { StreamdownTextPrimitive } from "@assistant-ui/react-streamdown";
<StreamdownTextPrimitive />
With Plugins (Recommended)
import { StreamdownTextPrimitive } from "@assistant-ui/react-streamdown";
import { code } from "@streamdown/code";
import { math } from "@streamdown/math";
import { mermaid } from "@streamdown/mermaid";
import "katex/dist/katex.min.css";
<StreamdownTextPrimitive
plugins={{ code, math, mermaid }}
shikiTheme={["github-light", "github-dark"]}
/>
Migration from react-markdown
If you're migrating from @assistant-ui/react-markdown, you can use the compatibility API:
import { StreamdownTextPrimitive } from "@assistant-ui/react-streamdown";
<StreamdownTextPrimitive
components={{
SyntaxHighlighter: MySyntaxHighlighter,
CodeHeader: MyCodeHeader,
}}
componentsByLanguage={{
mermaid: { SyntaxHighlighter: MermaidRenderer }
}}
/>
Props
mode | "streaming" | "static" | Rendering mode. Default: "streaming" |
plugins | PluginConfig | Streamdown plugins (code, math, mermaid, cjk) |
shikiTheme | [string, string] | Light and dark theme for Shiki |
components | object | Custom components including SyntaxHighlighter and CodeHeader |
componentsByLanguage | object | Language-specific component overrides |
preprocess | (text: string) => string | Text preprocessing function |
controls | boolean | object | Enable/disable UI controls for code blocks and tables |
containerProps | object | Props for the container div |
containerClassName | string | Class name for the container |
remarkRehypeOptions | object | Options passed to remark-rehype during processing |
BlockComponent | React.ComponentType | Custom component for rendering blocks |
parseMarkdownIntoBlocksFn | (md: string) => string[] | Custom block parsing function |
remend | object | Incomplete markdown auto-completion config |
linkSafety | object | Link safety confirmation config |
mermaid | object | Mermaid diagram rendering config |
allowedTags | object | HTML tags whitelist |
Differences from react-markdown
- Streaming optimization: Uses block-based rendering and
remend for better streaming performance
- Built-in highlighting: Shiki is integrated via
@streamdown/code plugin
- No smooth prop: Streaming animation is handled by streamdown's
mode and isAnimating
- Auto isAnimating: Automatically detects streaming state from message context
Advanced Usage
Custom Block Rendering
Use BlockComponent to customize how individual markdown blocks are rendered:
<StreamdownTextPrimitive
BlockComponent={({ content, index }) => (
<div key={index} className="my-block">
{/* Custom block rendering */}
</div>
)}
/>
Custom Block Parsing
Override the default block splitting logic:
<StreamdownTextPrimitive
parseMarkdownIntoBlocksFn={(markdown) => markdown.split(/\n{2,}/)}
/>
Using Hooks
import {
useIsStreamdownCodeBlock,
useStreamdownPreProps,
} from "@assistant-ui/react-streamdown";
function MyCodeComponent() {
const isCodeBlock = useIsStreamdownCodeBlock();
const preProps = useStreamdownPreProps();
if (!isCodeBlock) {
return <code className="inline-code">...</code>;
}
return <pre {...preProps}>...</pre>;
}
Performance Best Practices
-
Use memoized components: Custom SyntaxHighlighter and CodeHeader components should be memoized to avoid unnecessary re-renders.
-
Avoid inline function props: Define preprocess, parseMarkdownIntoBlocksFn, and other callbacks outside the render function or wrap them in useCallback.
-
Plugin configuration: Pass plugin objects by reference (not inline) to prevent recreation on each render.
const plugins = useMemo(() => ({ code, math }), []);
<StreamdownTextPrimitive plugins={plugins} />
<StreamdownTextPrimitive plugins={{ code, math }} />
License
MIT