Markdown streaming
When Markdown arrives incrementally - token by token from an LLM, for example - you can render it as it streams instead of waiting for the whole string. The display component fades in newly appended text and handles half-formed block elements gracefully while the rest is still arriving.
Streaming ships in the React Native package, on iOS and Android. Both props are stripped on web, where the current Markdown string renders without the fade-in animation or the incomplete-block handling below.
The standalone iOS and Android packages have no streaming API. You can still hand them a growing Markdown string and each update renders it, but new text does not fade in and half-formed tables, code blocks and math are not held back.
Building an LLM chat UI? react-native-streamdown is a sibling library that wraps this renderer for streaming - it repairs incomplete Markdown on the fly and moves parsing off the JS thread, while accepting every EnrichedMarkdownText prop.
It is not quite a drop-in, though: it requires Worklets Bundle Mode, which means bundleMode: true plus importForwarding for remend in babel.config.js, a getBundleModeMetroConfig entry in metro.config.js, and a Metro patch - without which the one-shot react-native bundle used for offline release bundling fails. Read its README's Required setup section before adopting it.
Incremental rendering
Feed the growing Markdown string to the display component and enable streamingAnimation (off by default). Only the tail - the new characters beyond the previous content - animates on each update, so the already-rendered text stays put:
<EnrichedMarkdownText markdown={partialMarkdown} streamingAnimation />
The tail is found by length, not by diffing: everything past the previous string's length fades in. Streaming therefore assumes append-only updates. If you replace markdown wholesale - a retry, a regeneration, switching to a different response - the fade will not line up with what actually changed.
Incomplete blocks
Some block elements can't be rendered until enough structure has arrived - a table needs its header and separator row, a fenced code block needs its closing fence. streamingConfig decides what happens in the meantime.
It takes two conditions, not one: streamingAnimation must be on and the flavor must be github. The filter runs only on the segmented renderer, so under the default flavor="commonmark" you get the tail fade and nothing else - streamingConfig is inert there. This is worth knowing because streamingAnimation is otherwise most natural with commonmark.
Tables (tableMode)
| Mode | Behavior |
|---|---|
'progressive' (default) | Renders the table row-by-row as content arrives; incomplete trailing rows are trimmed, and new rows fade in. |
'hidden' | Withholds the table until it is complete (followed by a blank line), avoiding partially-formed-table jank. |
Code blocks (codeBlockMode)
| Mode | Behavior |
|---|---|
'progressive' (default) | Streams the code line-by-line with a visible but non-interactive header (copying is disabled until the block completes); syntax highlighting is deferred until the closing fence so it does not flicker on every token. |
'hidden' | Withholds the whole block until its closing fence arrives, then shows it complete. |
Images, videos and links (mediaMode)
| Mode | Behavior |
|---|---|
'progressive' (default) | Shows a trailing image paragraph, link paragraph or <video> block once its syntax is complete. A half-streamed ;
${fence}
`;
export default function App() {
const [markdown, setMarkdown] = useState('');
// Reveal the document a few characters at a time to imitate an LLM stream.
useEffect(() => {
let i = 0;
const id = setInterval(() => {
i += 4;
setMarkdown(full.slice(0, i));
if (i >= full.length) clearInterval(id);
}, 60);
return () => clearInterval(id);
}, []);
return (
<EnrichedMarkdownText
markdown={markdown}
flavor="github"
streamingAnimation
streamingConfig={{ tableMode: 'hidden', codeBlockMode: 'progressive' }}
/>
);
}
Reference
streamingAnimation- fade in newly appended content. Defaults tofalse.streamingConfig-tableMode,codeBlockModeandmediaMode, each'progressive'or'hidden'. NeedsstreamingAnimationandflavor="github".- Markdown flavors - what else the flavor switch changes.
react-native-streamdown- sibling library for LLM output: incomplete-Markdown repair (remend) and off-thread processing (react-native-worklets Bundle Mode). Accepts everyEnrichedMarkdownTextprop plus aremendConfig:<StreamdownText markdown={partial} />. See the setup caveat above.