Skip to content

Renderer & Node Components

Use this page when you already know you need a specific exported component and want the shortest path to its props, events, CSS requirements, and the page that goes deeper.

If you are still deciding where to customize the pipeline, start with:

Quick reference

ComponentBest forKey props/eventsExtra CSS / peersTroubleshooting hooks
MarkdownRenderRendering full AST trees (default export)Props: content / nodes, custom-id, final, parse-options, custom-html-tags, is-dark, code-block-props, mermaid-props, d2-props, infographic-props; events: copy, handleArtifactClick, click, mouseover, mouseoutImport markstream-vue/index.css inside a reset-aware layer (CSS is scoped under an internal .markstream-vue container)Use setCustomComponents(customId, mapping) + custom-id to scope overrides; see CSS checklist
CodeBlockNodeMonaco-powered code blocks, streaming diffsnode, monacoOptions, stream, loading; events: copy, previewCode; slots header-left / header-right; diff hover actions live under monacoOptions (diffHunkActionsOnHover, diffHunkHoverHideDelayMs, onDiffHunkAction)Install stream-monaco (peer) + bundle Monaco workersBlank editor => check worker bundling + SSR guards
MarkdownCodeBlockNodeLightweight highlighting via shikinode, stream, loading; slots header-left / header-rightRequires shiki + stream-markdownUse for SSR-friendly or low-bundle scenarios
MermaidBlockNodeProgressive Mermaid diagramsnode, isDark, isStrict, maxHeight; emits copy, export, openModal, toggleModePeer mermaid >= 11; no extra CSS requiredFor async errors see /guide/mermaid
D2BlockNodeProgressive D2 diagramsnode, isDark, maxHeight, progressiveRender, progressiveIntervalMs; toolbar togglesPeer @terrastruct/d2; no extra CSSMissing peer falls back to source; see /guide/d2
MathBlockNode / MathInlineNodeKaTeX renderingnodeInstall katex and import katex/dist/katex.min.cssSSR requires client-only in Nuxt
ImageNodeCustom previews/lightboxesProps: fallback-src, show-caption, lazy, svg-min-height, use-placeholder; emits click, load, errorNone, but respects global CSSWrap in a custom component + setCustomComponents to intercept events
LinkNodeAnimated underline, tooltipscolor, underlineHeight, showTooltipNo extra CSSBrowser defaults can override a styles; import reset
VmrContainerNodeCustom ::: containersnode (name, attrs, loading, children)Minimal base CSS; override via setCustomComponentsJSON attrs are normalized onto node.attrs (keys without data-); invalid/partial JSON becomes attrs.attrs; args after name stored in attrs.args

TypeScript exports

markstream-vue exports renderer and component prop interfaces:

ts
import type {
  CodeBlockNodeProps,
  D2BlockNodeProps,
  InfographicBlockNodeProps,
  MermaidBlockNodeProps,
  NodeRendererProps,
  PreCodeNodeProps,
} from 'markstream-vue'
import type { CodeBlockNode } from 'stream-markdown-parser'

Notes:

  • NodeRendererProps matches <MarkdownRender> props.
  • CodeBlockNodeProps, MermaidBlockNodeProps, D2BlockNodeProps, InfographicBlockNodeProps, and PreCodeNodeProps all use CodeBlockNode for node (use language: 'mermaid' / language: 'd2' / language: 'd2lang' / language: 'infographic' to route specialized renderers).

Pick the right component quickly

  • Use MarkdownRender for almost every app-level integration.
  • Use CodeBlockNode, MermaidBlockNode, D2BlockNode, or MathBlockNode when you are composing lower-level node renderers yourself.
  • Use ImageNode, LinkNode, or VmrContainerNode when you only need to replace one built-in node with custom behavior.
  • If you render node components directly, wrap them in <div class="markstream-vue">...</div> so packaged CSS variables and component styles apply.

MarkdownRender

Main entry point that takes Markdown AST content (string or parsed structure) and renders with built-in node components.

Quick reference

  • Best for: full markdown documents in Vite, Nuxt, VitePress.
  • Key props: content / nodes, custom-id, final, parse-options, custom-html-tags
  • CSS order: import reset first, then add markstream-vue/index.css inside @layer components.

CSS scope

markstream-vue scopes its packaged CSS under an internal .markstream-vue container to reduce global style conflicts.

  • When you use MarkdownRender, this container is already present.
  • When you render node components directly, wrap them with <div class="markstream-vue">...</div> so theme variables and component styles apply.

Usage ladder

vue
<script setup lang="ts">
import MarkdownRender from 'markstream-vue'

const md = '# Hello\n\nUse custom-id to scope styles.'
</script>

<template>
  <MarkdownRender custom-id="docs" :content="md" />
</template>
ts
// Register custom nodes
import { setCustomComponents } from 'markstream-vue'
import CustomImageNode from './CustomImageNode.vue'

setCustomComponents('docs', {
  image: CustomImageNode,
})
css
/* styles/main.css */
@import 'modern-css-reset';
@tailwind base;

@layer components {
  @import 'markstream-vue/index.css';
}

[data-custom-id='docs'] .prose {
  max-width: 720px;
}

Performance props

  • Batch rendering: batchRendering, initialRenderBatchSize, renderBatchSize, renderBatchDelay, and renderBatchBudgetMs control how many nodes switch from skeletons to real components per frame. This only activates when virtualization is disabled (:max-live-nodes="0").
  • Deferred heavy nodes: deferNodesUntilVisible and viewportPriority are enabled by default so Mermaid, D2, Monaco, and KaTeX only load near the viewport.
  • Virtualization window: maxLiveNodes limits how many rendered nodes stay in the DOM, and liveNodeBuffer controls overscan. See Performance.
  • Code block fallback: use renderCodeBlocksAsPre and codeBlockStream to downgrade standard code fences to <pre><code> or disable streaming updates during heavy workloads.

Common issues

  • Broken styles: start with the CSS checklist.
  • Utility class leakage: pass custom-id and scope overrides with [data-custom-id="docs"].
  • SSR errors: wrap browser-only peers such as Mermaid, D2, and Monaco with client-only guards.

Use this when

  • You want the default parser + renderer path with the fewest moving parts.
  • You need one integration point for streaming, virtualization, batching, custom tags, and scoped overrides.
  • You want to choose between content and nodes without wiring individual node renderers yourself.

Most common combinations

  • content + custom-id: static or lightly customized documents.
  • nodes + final: streaming or server-preparsed workflows.
  • custom-html-tags + setCustomComponents(customId, mapping): trusted custom tags such as thinking.
  • renderCodeBlocksAsPre: downgrade heavy code blocks during SSR or constrained environments.

CodeBlockNode

Monaco-backed code block renderer with streaming diff support and interactive toolbar actions.

  • Best for: rich code review, diff inspection, live patches, hover actions.
  • Key props: node, monacoOptions, stream, loading
  • Events: copy, previewCode
  • Slots: header-left, header-right
  • Peers: stream-monaco, plus Monaco worker setup in your bundler
  • Common gotcha: if the editor area is blank, verify worker bundling and SSR guards first

Reach for this when the code block itself is part of the product experience. If you only need syntax highlighting, prefer MarkdownCodeBlockNode.

Deep dive: CodeBlockNode, Monaco

MarkdownCodeBlockNode

Lightweight syntax-highlighted code block renderer powered by shiki and stream-markdown.

  • Best for: SSR-friendly docs, blog-style pages, smaller bundles
  • Key props: node, stream, loading
  • Slots: header-left, header-right
  • Peers: shiki, stream-markdown
  • Common gotcha: if highlighting never appears, confirm both peers are installed and loaded in the environment where rendering happens

Choose this when you do not need Monaco's editing surface or diff interactions.

MermaidBlockNode

Progressive Mermaid renderer with copy/export/modal hooks.

  • Best for: large Mermaid graphs, AI-generated diagrams, user-triggered export
  • Key props: node, isDark, isStrict, maxHeight
  • Events: copy, export, openModal, toggleMode
  • Peer: mermaid >= 11
  • Common gotcha: SSR setups must gate Mermaid initialization to the client

Deep dive: Mermaid, MermaidBlockNode

D2BlockNode

Progressive D2 diagram renderer for structured architecture diagrams and sequence-like layouts.

  • Best for: D2-driven docs, generated architecture views, progressive rendering
  • Key props: node, isDark, maxHeight, progressiveRender, progressiveIntervalMs
  • Peer: @terrastruct/d2
  • Common gotcha: missing peer falls back to source display instead of the rendered diagram

Deep dive: D2

MathBlockNode / MathInlineNode

KaTeX-backed block and inline math renderers.

  • Best for: technical docs, formulas, AI math responses
  • Key prop: node
  • Peer: katex
  • Required CSS: katex/dist/katex.min.css
  • Common gotcha: invisible math usually means KaTeX CSS is missing, not that parsing failed

ImageNode

Built-in image renderer with sensible defaults for captioning, fallbacks, and lazy behavior.

  • Best for: custom previews, analytics hooks, lightbox integration
  • Key props: fallback-src, show-caption, lazy, svg-min-height, use-placeholder
  • Events: click, load, error
  • Common pattern: wrap this with a custom component and register it through setCustomComponents(customId, { image: CustomImageNode })

Deep dive: ImageNode

LinkNode

Link renderer with underline animation and optional tooltip behavior.

  • Best for: docs sites and chat surfaces where link affordance needs to match your design system
  • Key props: color, underlineHeight, showTooltip
  • Common gotcha: browser defaults or resets can override anchor styles, so verify CSS order before assuming the component is broken

VmrContainerNode

Renderer for ::: containers and other normalized block containers coming from the parser.

  • Best for: callouts, notices, AI-specific block types, container-style custom components
  • Key prop: node with name, attrs, loading, and children
  • Normalization detail: JSON attrs are normalized onto node.attrs; invalid or partial JSON is preserved under attrs.attrs; args after the container name are stored in attrs.args
  • Common pattern: use this together with custom-html-tags or parser hooks when you need trusted structured blocks

Direct node rendering checklist

When you bypass MarkdownRender and mount node components directly:

  • Wrap them in a .markstream-vue container.
  • Import the same CSS you would for the full renderer.
  • Install and guard only the peers needed by the nodes you actually render.
  • Scope any replacement styles with a parent selector or data-custom-id equivalent.

Need a different layer of the stack?

  • Use API Reference for parser helpers, setCustomComponents, and AST hooks.
  • Use Props & Options for full renderer prop behavior.
  • Use Troubleshooting if the right component is already chosen but CSS, peers, or SSR still misbehave.