Components/Stream Markdown

Stream Markdown

A Markdown renderer built for streaming output.

VerifiedSince 0.3.9

Usage

Basic

While streaming, an unclosed tail fence defers rendering instead of flashing in half-written.

Loading demo...

Best Practices

  • Keep sanitize on: model output is untrusted, and this is where it becomes DOM.
  • Set streaming to false when generation ends, or the caret stays at the tail.
  • In custom renderers, show a loading state while closed is false instead of parsing a partial block.
  • Key renderers by the lowercase first word of the fence (```chart → chart).
  • Use TxMarkdownView for static Markdown; it needs none of the block and caret machinery.

API Reference

Props

NameTypeDefaultDescription
contentstring—The Markdown source. Required.
streamingbooleanfalseOutput is still arriving; drives the caret, the reveal, and deferred tail-fence rendering.
reveal'aurora' | 'hue' | 'blur' | 'languid' | 'none''aurora'How new text enters, with TxStreamText's presets; languid keeps its slow blur but not its rise.
caretbooleantrueShows the Tuff caret at the write head while streaming; switched off, it goes at once.
sanitizebooleantrueSanitizes HTML through DOMPurify.
theme'light' | 'dark' | 'auto''auto'Color theme; auto follows the environment.
renderersRecord<string, StreamMarkdownBlockRenderer>—Block renderers by fence language; each receives StreamMarkdownBlockContext as props.
blockRemoteImagesbooleantrueShows remote images (http(s)://, //) as a placeholder until the reader loads them.
blockedImageTextstring'Remote image blocked'Title of the blocked-image placeholder.
loadImageOnceTextstring'Load this image'Text of the button that loads only this image.
allowSessionImagesTextstring'Allow for this conversation'Text of the button that allows every remote image for the conversation.
copyTableTextstring'Copy CSV'Text of a table's copy-as-CSV button.
copiedTableTextstring'Copied'Button text shown briefly after a successful copy.

Overview

  • The document renders block by block, so appended text never reflows the whole page.
  • While streaming, the still-growing tail fence defers its final rendering until it closes; renderers get closed: false only for that fence.
  • Unregistered languages use the built-ins: TxMermaidBlock for mermaid, TxCodeBlock for the rest.
  • The caret is TxStreamText's: right after the last character of a paragraph, heading, or quote, otherwise on its own line below. Under reduced motion nothing enters and the caret rests.
  • DOMPurify loads dynamically; with sanitize on, nothing renders until it loads, or at all if loading fails. Sanitizing is no substitute for a server-side trust boundary.
  • Image consent is module-level and shared by every instance; call the exported resetRemoteImagePolicy() when the conversation changes.
  • Each instance owns its own Marked (gfm, breaks); the GitHub Markdown sheet is scoped under :where(.tx-md), leaving the host's .markdown-body alone.