# React Components & API markstream-react provides the same powerful components as markstream-vue, but built for React. All components support React 18+ with full TypeScript support. The root `markstream-react`, `markstream-react/next`, and `markstream-react/server` entrypoints all ship declaration files. Shared renderer and component types such as `NodeRendererProps`, `NodeRendererCodeBlockProps`, `NodeComponentProps`, `RenderContext`, `RenderNodeFn`, `CustomComponentMap`, `CodeBlockMonacoOptions`, `MarkdownCodeBlockNodeProps`, `ListItemNodeProps`, `HtmlPreviewFrameProps`, `TooltipProps`, `TooltipPlacement`, and `LinkNodeStyleProps` can be imported directly from the entrypoint you use. ## Main Component: MarkdownRender The primary component for rendering markdown content in React. ### Props `MarkdownRender` uses the `NodeRendererProps` interface from `markstream-react`. #### Core props | Prop | Type | Default | Description | |------|------|---------|-------------| | `content` | `string` | - | Markdown content to render | | `nodes` | `BaseNode[]` | - | Pre-parsed AST nodes (typically `ParsedNode[]` from the parser) | | `customId` | `string` | - | Identifier for scoping custom components and CSS | | `final` | `boolean` | `false` | Marks end-of-stream; stops emitting streaming `loading` nodes | | `parseOptions` | `ParseOptions` | - | Parser options and token hooks (only when `content` is provided) | | `customHtmlTags` | `readonly string[]` | - | HTML-like tags emitted as custom nodes (e.g. `thinking`) | | `customMarkdownIt` | `(md: MarkdownIt) => MarkdownIt` | - | Customize the internal MarkdownIt instance | | `debugPerformance` | `boolean` | `false` | Log parse/render timing and virtualization stats (dev only) | | `isDark` | `boolean` | `false` | Theme flag forwarded to heavy nodes; adds `.dark` to the root container | | `indexKey` | `number \| string` | - | Key prefix when rendering multiple instances in lists | | `typewriter` | `boolean` | `true` | Enable the non-code-node enter transition | | `showTooltips` | `boolean` | `true` | Global tooltip switch for `LinkNode` and code block nodes | #### Streaming & heavy-node toggles | Prop | Default | Description | |------|---------|-------------| | `renderCodeBlocksAsPre` | `false` | Render code blocks as `
` (Mermaid/D2/Infographic blocks will also fall back) |
| `codeBlockStream` | `true` | Stream code block updates as content arrives |
| `viewportPriority` | `true` | Defer heavy work (Monaco/Mermaid/D2/KaTeX) until near viewport |
| `deferNodesUntilVisible` | `true` | Render heavy nodes as placeholders until visible (non-virtualized mode only) |

#### Performance (virtualization & batching)

| Prop | Default | Description |
|------|---------|-------------|
| `maxLiveNodes` | `320` | Max fully rendered nodes kept in DOM (set `0` to disable virtualization) |
| `liveNodeBuffer` | `60` | Overscan buffer around the focus range |
| `batchRendering` | `true` | Incremental batch rendering when virtualization is disabled |
| `initialRenderBatchSize` | `40` | Nodes rendered immediately before batching starts |
| `renderBatchSize` | `80` | Nodes rendered per batch tick |
| `renderBatchDelay` | `16` | Extra delay (ms) before each batch after rAF |
| `renderBatchBudgetMs` | `6` | Time budget (ms) before adaptive batch sizes shrink |
| `renderBatchIdleTimeoutMs` | `120` | Timeout (ms) for `requestIdleCallback` slices |

#### Global code block options

| Prop | Type | Description |
|------|------|-------------|
| `codeBlockDarkTheme` | `CodeBlockMonacoTheme` | Monaco dark theme object forwarded to every `CodeBlockNode` |
| `codeBlockLightTheme` | `CodeBlockMonacoTheme` | Monaco light theme object forwarded to every `CodeBlockNode` |
| `codeBlockMonacoOptions` | `CodeBlockMonacoOptions` | Options forwarded to `stream-monaco`, including diff hover-action settings like `diffHunkActionsOnHover`, `diffHunkHoverHideDelayMs`, and `onDiffHunkAction` |
| `codeBlockMinWidth` | `string \| number` | Min width forwarded to `CodeBlockNode` |
| `codeBlockMaxWidth` | `string \| number` | Max width forwarded to `CodeBlockNode` |
| `codeBlockProps` | `NodeRendererCodeBlockProps` | Extra props forwarded to every code-block renderer (`CodeBlockNode` / `MarkdownCodeBlockNode`) |
| `mermaidProps` | `Partial>` | Extra props forwarded to Mermaid fences and custom `mermaid` renderers |
| `d2Props` | `Partial>` | Extra props forwarded to D2 fences and custom `d2` renderers |
| `infographicProps` | `Partial>` | Extra props forwarded to infographic fences and custom `infographic` renderers |
| `themes` | `CodeBlockMonacoTheme[]` | Theme list forwarded to `stream-monaco` |

#### Heavy renderer prop forwarding

`NodeRenderer` can tune heavy blocks directly:

```tsx

```

Streaming notes:
- Keep `viewportPriority` enabled to prevent offscreen Mermaid / Monaco / D2 work from running while text is still streaming.
- For high-frequency SSE, prefer passing `nodes` instead of reparsing the full `content` string every chunk.
- Common Mermaid tuning keys: `renderDebounceMs`, `contentStableDelayMs`, `previewPollDelayMs`, `previewPollMaxDelayMs`, `previewPollMaxAttempts`.

`NodeRendererCodeBlockProps` follows the public `CodeBlockNode` prop surface except for `node`, so you get completion for flags like `showHeader`, `showFontSizeButtons`, and `showTooltips` without dropping to `any`.

```tsx
import type { NodeRendererCodeBlockProps } from 'markstream-react'

const codeBlockProps: NodeRendererCodeBlockProps = {
  showHeader: false,
  showFontSizeButtons: false,
  showTooltips: false,
}
```

#### Events

| Prop | Type | Description |
|------|------|-------------|
| `onCopy` | `(code: string) => void` | Fired when code blocks emit copy events |
| `onHandleArtifactClick` | `(payload: any) => void` | Fired on artifact/preview clicks |
| `onClick` | `(event: React.MouseEvent) => void` | Click handler for the renderer root |
| `onMouseOver` | `(event: React.MouseEvent) => void` | Mouseover handler for the renderer root |
| `onMouseOut` | `(event: React.MouseEvent) => void` | Mouseout handler for the renderer root |

### Usage

```tsx
import MarkdownRender from 'markstream-react'

function App() {
  const markdown = `# Hello React!

This is markstream-react.`

  return (
    
  )
}
```

## Code Block Components

### MarkdownCodeBlockNode

Lightweight code highlighting using Shiki.

```tsx
import { MarkdownCodeBlockNode } from 'markstream-react'

function CodeBlock() {
  const codeNode = {
    type: 'code_block',
    language: 'javascript',
    code: 'const hello = "world"',
    raw: 'const hello = "world"'
  }

  const handleCopy = () => {
    alert('Code copied!')
  }

  return (
    
) } ``` ### CodeBlockNode Feature-rich Monaco-powered code blocks. ```tsx import { CodeBlockNode } from 'markstream-react' function MonacoCodeBlock() { const codeNode = { type: 'code_block', language: 'typescript', code: 'const greeting: string = "Hello"', raw: 'const greeting: string = "Hello"' } const handleCopy = (code: string) => { console.log('Code copied:', code) } const handlePreviewCode = (artifact: any) => { console.log('Preview code:', artifact) } return (
) } ``` ## Math Components ### MathBlockNode Renders block-level math formulas with KaTeX. ```tsx import { MathBlockNode } from 'markstream-react' function MathBlock() { const mathNode = { type: 'math_block', content: '\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}', raw: '\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}' } return (
) } ``` ### MathInlineNode Renders inline math formulas. ```tsx import { MathInlineNode } from 'markstream-react' function MathInline() { const inlineMathNode = { type: 'math_inline', content: 'E = mc^2', raw: 'E = mc^2' } return (

The formula is: {' '}

) } ``` ## Mermaid Diagrams ### MermaidBlockNode Progressive Mermaid diagram rendering. #### Quick reference - **Tooltip control**: `showTooltips` defaults to `true`; set `false` to disable header action tooltips. ```tsx import { MermaidBlockNode } from 'markstream-react' function MermaidDiagram() { const mermaidNode = { type: 'code_block', language: 'mermaid', code: `graph TD A[Start] --> B{Is it working?} B -->|Yes| C[Great!]`, raw: '' } const handleExport = (ev: any) => { console.log('Mermaid SVG:', ev.svgString) } return (
) } ``` Event notes: - `onCopy(code: string)` receives the source text directly (no `MermaidBlockEvent` wrapper in React). - `onExport`, `onOpenModal`, `onToggleMode` receive `MermaidBlockEvent` and support `ev.preventDefault()` to stop the default behavior. - `onToggleMode` signature: `(target: 'source' | 'preview', ev)`. - For renderer-wide usage, prefer passing these knobs through `mermaidProps` on `MarkdownRender` / `NodeRenderer`. ## D2 Diagrams ### D2BlockNode Progressive D2 diagram rendering with source fallback. ```tsx import { D2BlockNode } from 'markstream-react' function D2Diagram() { const d2Node = { type: 'code_block', language: 'd2', code: `direction: right Client -> API: request API -> DB: query`, raw: '' } return (
) } ``` ## Other Node Components ### HeadingNode ```tsx import { HeadingNode } from 'markstream-react' function CustomHeading() { const headingNode = { type: 'heading', level: 1 } return Hello World } ``` ### ParagraphNode ```tsx import { ParagraphNode } from 'markstream-react' function CustomParagraph() { const paragraphNode = { type: 'paragraph' } return ( This is a {' '} bold {' '} word. ) } ``` ### ListNode ```tsx import { ListNode, renderNode } from 'markstream-react' function CustomList() { const listNode = { type: 'list', ordered: false, items: [ { type: 'list_item', children: [ { type: 'paragraph', children: [{ type: 'text', content: 'Item 1' }] } ] }, { type: 'list_item', children: [ { type: 'paragraph', children: [{ type: 'text', content: 'Item 2' }] } ] } ] } const ctx = { events: {} } return } ``` ### LinkNode ```tsx import { LinkNode } from 'markstream-react' function CustomLink() { const linkNode = { type: 'link', href: 'https://example.com', title: 'Example', text: 'Click me' } return ( ) } ``` ### ImageNode ```tsx import { ImageNode } from 'markstream-react' function CustomImage() { const imageNode = { type: 'image', src: 'https://example.com/image.jpg', alt: 'Example image', title: 'Example', raw: '![Example image](https://example.com/image.jpg)' } const handleClick = () => { console.log('Image clicked!') } const handleLoad = () => { console.log('Image loaded!') } return ( ) } ``` ## Utility Functions ### getMarkdown Get a configured markdown-it instance. ```tsx import { getMarkdown } from 'stream-markdown-parser' const md = getMarkdown('my-msg-id', { html: true, linkify: true, typographer: true }) const tokens = md.parse('# Hello World') ``` ### parseMarkdownToStructure Parse markdown string to AST structure. ```tsx import { getMarkdown, parseMarkdownToStructure } from 'stream-markdown-parser' const md = getMarkdown() const nodes = parseMarkdownToStructure('# Title\n\nContent here...', md) // Use with MarkdownRender // ``` ### Optional workers (Mermaid / KaTeX) Mermaid/KaTeX auto-load when installed. If you want off‑thread parsing/rendering, inject workers: ```tsx import { setKaTeXWorker, setMermaidWorker } from 'markstream-react' import KatexWorker from 'markstream-react/workers/katexRenderer.worker?worker' import MermaidWorker from 'markstream-react/workers/mermaidParser.worker?worker' setMermaidWorker(new MermaidWorker()) setKaTeXWorker(new KatexWorker()) ``` ## Custom Component API ### Props Interface All custom node components receive these props: ```tsx import type { CustomComponentMap, NodeComponentProps, RenderContext, RenderNodeFn } from 'markstream-react' interface NodeComponentProps { node: TNode // The parsed node data ctx?: RenderContext // Renderer context (themes, events, flags) renderNode?: RenderNodeFn // Helper to render child nodes indexKey?: React.Key // Unique key for the node customId?: string // Custom ID for scoping isDark?: boolean typewriter?: boolean children?: React.ReactNode } ``` `CustomComponentMap` is the typed mapping shape accepted by `setCustomComponents(...)`. ### Example Custom Component ```tsx import React from 'react' interface CustomParagraphProps { node: { type: string children: Array<{ type: string content?: string children?: any[] }> } indexKey?: number | string customId?: string } function CustomParagraph({ node, indexKey, customId }: CustomParagraphProps) { return (

{node.children.map((child, i) => ( {child.content || ''} ))}

) } // Usage function App() { const paragraphNode = { type: 'paragraph', children: [ { type: 'text', content: 'Custom paragraph content' } ] } return } ``` ## Context + Custom Components You can use React Context inside custom node components, while still registering them via `setCustomComponents`: ```tsx import MarkdownRender, { setCustomComponents } from 'markstream-react' import React, { createContext, useContext } from 'react' const ThemeContext = createContext<'light' | 'dark'>('light') function CustomHeading({ node, customId }: any) { const theme = useContext(ThemeContext) const level = node.level || 1 const Tag = `h${level}` as keyof JSX.IntrinsicElements return ( {node.children?.map((child: any, i: number) => ( {child.content || ''} ))} ) } setCustomComponents('docs', { heading: CustomHeading }) function App() { const markdown = `# Custom Heading This uses a custom heading component. ` return ( ) } ``` ## Streaming Support markstream-react supports streaming markdown content: ```tsx import MarkdownRender from 'markstream-react' import { useEffect, useState } from 'react' function StreamingDemo() { const [content, setContent] = useState('') const fullContent = `# Streaming Demo This content streams in **small chunks**. ` useEffect(() => { let i = 0 const interval = setInterval(() => { if (i < fullContent.length) { setContent(prev => prev + fullContent[i]) i++ } else { clearInterval(interval) } }, 30) return () => clearInterval(interval) }, []) return } ``` ## TypeScript Support markstream-react includes full TypeScript definitions: ```tsx import type { NodeComponentProps, NodeRendererProps } from 'markstream-react' import type { ParsedNode } from 'stream-markdown-parser' import MarkdownRender from 'markstream-react' function App() { const markdown = '# Hello TypeScript!' const nodes: ParsedNode[] = [] return } ``` Code block prop interfaces (`CodeBlockNodeProps`, `MermaidBlockNodeProps`, `D2BlockNodeProps`, `InfographicBlockNodeProps`, `PreCodeNodeProps`) all use `node: CodeBlockNode` from `stream-markdown-parser` (use `language: 'mermaid'` / `language: 'd2'` / `language: 'd2lang'` / `language: 'infographic'` when targeting specialized renderers). ## Next.js Best Practices Use the dedicated Next SSR entrypoints instead of `mounted` guards or `ssr: false`. ```tsx import MarkdownRender from 'markstream-react/next' export default function MarkdownPage() { return } ``` For a pure server render path: ```tsx import MarkdownRender from 'markstream-react/server' export default function MarkdownPage() { return } ``` See [React Next SSR](/guide/react-next-ssr) for the full entrypoint model. ## Hooks Integration You can easily integrate with React hooks: ```tsx import type { ChangeEvent } from 'react' import MarkdownRender from 'markstream-react' import { useCallback, useMemo, useState } from 'react' function MarkdownEditor() { const [content, setContent] = useState('# Edit me!') const [theme, setTheme] = useState('light') const memoizedContent = useMemo(() => content, [content]) const handleChange = useCallback((e: ChangeEvent) => { setContent(e.target.value) }, []) return (