React renderer that consumes the structured AST output from stream-markdown-parser and renders it with lightweight semantic HTML components. This is the React counter-part to the Vue renderer that powers markstream-vue.
pnpm --filter markstream-react devpnpm --filter markstream-react build
pnpm --filter markstream-react build:analyze
pnpm --filter markstream-react size:checkimport NodeRenderer from 'markstream-react'
import 'markstream-react/index.css'
export default function Article({ markdown }: { markdown: string }) {
return (
<NodeRenderer content={markdown} />
)
}If your app scales root font size on mobile (html / body), use markstream-react/index.px.css to prevent rem-based global scaling side effects.
You can also pass a pre-parsed nodes array if you already have AST data.
- Optional peers are not bundled; install only what you use (
stream-monaco,stream-markdown,mermaid,katex, etc.). - Infrequent language icons are split into an async chunk and loaded on demand.
- To avoid first-hit fallback icons, preload once when the app is idle:
import { preloadExtendedLanguageIcons } from 'markstream-react'
if (typeof window !== 'undefined')
void preloadExtendedLanguageIcons()- Non-Tailwind projects: keep importing
markstream-react/index.css(includes precompiled utilities for the renderer). - Tailwind projects (avoid duplicate utilities): import
markstream-react/index.tailwind.cssand addrequire('markstream-react/tailwind')to yourtailwind.config.jscontent.
Custom tag-like blocks are exposed as nodes with type: 'thinking' (the tag name, no angle brackets) when you register the tag in customHtmlTags or register a custom component mapping for it.
import type { NodeComponentProps } from 'markstream-react'
import NodeRenderer, { setCustomComponents } from 'markstream-react'
function ThinkingNode(props: NodeComponentProps<{ type: 'thinking', content: string }>) {
const { node, ctx } = props
return (
<div className="thinking-node">
<div className="thinking-title">Thinking</div>
<NodeRenderer
content={node.content}
customId={ctx?.customId}
isDark={ctx?.isDark}
typewriter={false}
batchRendering={false}
deferNodesUntilVisible={false}
viewportPriority={false}
maxLiveNodes={0}
/>
</div>
)
}
setCustomComponents('chat', { thinking: ThinkingNode })