Stream Markdown
A Markdown renderer built for streaming output.
Usage
Basic
While streaming, an unclosed tail fence defers rendering instead of flashing in half-written.
Loading demo...
Best Practices
- Keep
sanitizeon: model output is untrusted, and this is where it becomes DOM. - Set
streamingto false when generation ends, or the caret stays at the tail. - In custom renderers, show a loading state while
closedis false instead of parsing a partial block. - Key
renderersby the lowercase first word of the fence (```chart→chart). - Use
TxMarkdownViewfor static Markdown; it needs none of the block and caret machinery.
API Reference
Props
| Name | Type | Default | Description |
|---|---|---|---|
content | string | — | The Markdown source. Required. |
streaming | boolean | false | Output 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. |
caret | boolean | true | Shows the Tuff caret at the write head while streaming; switched off, it goes at once. |
sanitize | boolean | true | Sanitizes HTML through DOMPurify. |
theme | 'light' | 'dark' | 'auto' | 'auto' | Color theme; auto follows the environment. |
renderers | Record<string, StreamMarkdownBlockRenderer> | — | Block renderers by fence language; each receives StreamMarkdownBlockContext as props. |
blockRemoteImages | boolean | true | Shows remote images (http(s)://, //) as a placeholder until the reader loads them. |
blockedImageText | string | 'Remote image blocked' | Title of the blocked-image placeholder. |
loadImageOnceText | string | 'Load this image' | Text of the button that loads only this image. |
allowSessionImagesText | string | 'Allow for this conversation' | Text of the button that allows every remote image for the conversation. |
copyTableText | string | 'Copy CSV' | Text of a table's copy-as-CSV button. |
copiedTableText | string | '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: falseonly for that fence. - Unregistered languages use the built-ins:
TxMermaidBlockformermaid,TxCodeBlockfor 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
sanitizeon, 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-bodyalone.