Skip to main content
In streaming mode, Streamdown splits the incoming Markdown string into discrete blocks using parseMarkdownIntoBlocks. Each block is rendered by a Block component instance. Block is exported so you can build a custom BlockComponent that wraps or extends its behavior.
import { Block, type BlockProps } from "streamdown";

How Block is used internally

In mode="streaming", Streamdown calls parseMarkdownIntoBlocksFn to produce an array of Markdown strings (one per block). For each string, it renders:
<BlockComponent
  content={block}
  index={index}
  isIncomplete={isIncomplete}
  dir={resolvedDir}
  animatePlugin={animatePlugin}
  shouldParseIncompleteMarkdown={...}
  shouldNormalizeHtmlIndentation={...}
  components={mergedComponents}
  rehypePlugins={mergedRehypePlugins}
  remarkPlugins={mergedRemarkPlugins}
/>
By default, BlockComponent is the built-in Block. Blocks are keyed by their index so React can reconcile updates without unmounting stable blocks when content grows. In mode="static", Streamdown bypasses block splitting and renders the entire Markdown string in a single Markdown call. Block is not used.

BlockProps

Block accepts BlockProps, which extends the Options type from the internal Markdown renderer (equivalent to react-markdown’s options) with streaming-specific fields.
content
string
required
The raw Markdown string for this block. Streamdown passes one element from the parseMarkdownIntoBlocksFn output. The block has already had remend and literal-tag preprocessing applied by the parent Streamdown component.
index
number
required
Zero-based position of this block in the full block array. Used for stable keying and is accessible to custom BlockComponent implementations.
Block receives index but does not pass it to the DOM. It is stripped before rendering to avoid unknown-prop warnings.
isIncomplete
boolean
required
true when this block is the last block and isAnimating is true and the block contains an incomplete code fence. Components can read this value via the useIsCodeFenceIncomplete hook to conditionally suppress rendering features (e.g. copy buttons) until the fence is closed.
import { useIsCodeFenceIncomplete } from "streamdown";

function MyCopyButton() {
  const isIncomplete = useIsCodeFenceIncomplete();
  if (isIncomplete) return null;
  return <button>Copy</button>;
}
dir
"ltr" | "rtl"
Resolved text direction for this block. When Streamdown is configured with dir="auto", each block’s direction is detected individually using the Unicode first-strong-character algorithm. For dir="ltr" or dir="rtl", all blocks receive the same value. When no dir prop is set on Streamdown, this is undefined and no dir attribute is written to the DOM.Block wraps the rendered Markdown in a <div dir={dir} style={{ display: "contents" }}> when a direction is present.
shouldParseIncompleteMarkdown
boolean
required
Mirrors the parent Streamdown’s parseIncompleteMarkdown prop. Block receives this for internal bookkeeping; the actual preprocessing is performed by Streamdown before blocks are created.
This prop is not forwarded to the DOM or to the underlying Markdown renderer.
shouldNormalizeHtmlIndentation
boolean
required
Mirrors the parent Streamdown’s normalizeHtmlIndentation prop. When true, Block calls normalizeHtmlIndentation on content before passing it to the Markdown renderer, stripping 4+ leading spaces from HTML tag lines to prevent them from being interpreted as code blocks.
animatePlugin
AnimatePlugin | null
An AnimatePlugin instance created by createAnimatePlugin. When provided and isAnimating is true, Block reads the previous render’s character count from the plugin before rendering, ensuring only newly streamed characters receive entrance animations.Pass null or omit to disable animation tracking for this block.
components
Components
Custom React components for HTML elements, forwarded directly to the underlying Markdown renderer. Inherits from the merged components computed by Streamdown.
rehypePlugins
Pluggable[]
Rehype plugins forwarded to the Markdown renderer. Streamdown passes its merged pipeline (default plugins + any added by plugins, allowedTags, literalTagContent, and the animate plugin).
remarkPlugins
Pluggable[]
Remark plugins forwarded to the Markdown renderer. Streamdown passes its merged pipeline (default plugins + any added by plugins.math and plugins.cjk).

Memoization

Block is wrapped in React.memo with a custom comparison function. It skips re-renders unless one of the following changes:
  • content
  • shouldNormalizeHtmlIndentation
  • index
  • isIncomplete
  • dir
  • components (shallow key + value comparison)
  • rehypePlugins (reference comparison)
  • remarkPlugins (reference comparison)
This makes streaming updates efficient: only the last block (the one receiving new tokens) re-renders on each tick. All other blocks stay mounted and frozen.

When to use a custom BlockComponent

Use the BlockComponent prop on Streamdown to replace Block when you need to:
  • Add a per-block error boundary
  • Inject per-block wrapper elements (e.g. for layout or animation)
  • Instrument rendering for analytics or debugging
  • Apply custom memoization strategies
import { Block, type BlockProps } from "streamdown";

function TrackedBlock(props: BlockProps) {
  // Track renders in development
  if (process.env.NODE_ENV === "development") {
    console.log("Block render", props.index, props.content.length);
  }

  return <Block {...props} />;
}

export function App({ markdown, isStreaming }) {
  return (
    <Streamdown
      isAnimating={isStreaming}
      BlockComponent={TrackedBlock}
    >
      {markdown}
    </Streamdown>
  );
}
Always delegate to the built-in Block (or a close equivalent) inside your custom BlockComponent. The built-in component handles the BlockIncompleteContext, direction wrapping, HTML indentation normalization, and animate plugin state bookkeeping that Streamdown’s other features depend on.

Build docs developers (and LLMs) love