Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/vercel/streamdown/llms.txt

Use this file to discover all available pages before exploring further.

One of Streamdown’s most powerful features is its ability to intelligently parse and style incomplete Markdown blocks using the remend package. This feature, called unterminated block parsing, ensures that your streaming content looks polished even before the AI finishes its response.

What is self-healing Markdown?

When an AI model streams Markdown token-by-token, your application receives a sequence of partial strings:
"**This"
"**This is"
"**This is bold"
"**This is bold**"
At each intermediate step, the string is not valid Markdown. Without intervention, the renderer either displays raw syntax characters or produces no formatting at all. Self-healing Markdown is the practice of completing those open markers just-in-time for rendering, then seamlessly updating when the real closing syntax arrives. Streamdown does this automatically by running the remend preprocessor on every content update before the string enters the unified/remark pipeline.

How Streamdown detects incomplete blocks

Streamdown uses two complementary detection layers:
  1. remend — operates on the raw string before parsing. It counts opening and closing markers (bold **, italic *, inline code `, etc.) and appends closing syntax where an imbalance is detected.
  2. hasIncompleteCodeFence — walks the parsed block line-by-line per the CommonMark spec. It tracks fence characters and lengths so it can correctly identify an unclosed ``` or ~~~ block, which requires structural analysis rather than simple marker counting.
The preprocessing step happens entirely on the string level — before any AST is built — which keeps it fast and avoids interfering with the remark/rehype plugin chain.

Supported incomplete patterns

Bold (**text)

**This is bold text that hasn't been closed yet
The parser detects the opening ** and appends ** so the text renders as bold.

Italic (*text or _text)

*This is italic text
_This is also italic
Single asterisks and underscores are each handled by a dedicated sub-handler. Underscores inside words (e.g. user_name) are left untouched.

Bold italic (***text)

***This is bold and italic
Triple asterisks for combined formatting are completed with ***.

Inline code (`code)

`const foo = "bar
An unclosed backtick is completed with a matching `.

Strikethrough (~~text)

~~This text is being crossed out
Double-tilde formatting is completed automatically.

Single tilde escape (20~25)

20~25°C
A single ~ between word characters is escaped to \~ to prevent GFM from misinterpreting it as a strikethrough marker. For example, 20~25°C renders as 20~25°C rather than applying strikethrough. Intentional ~~double tilde~~ strikethrough is unaffected. Streamdown handles several incomplete link scenarios: Incomplete link text:
[Click here
Completes to: [Click here](streamdown:incomplete-link) Incomplete URL:
[Click here](https://exampl
Completes to: [Click here](streamdown:incomplete-link) The placeholder URL streamdown:incomplete-link ensures the link renders visually without navigating anywhere. Text-only mode: If you prefer to show plain text while the link is incomplete, use the linkMode option:
<Streamdown remend={{ linkMode: 'text-only' }}>
  {markdown}
</Streamdown>
With linkMode: 'text-only', [Click here renders as plain text Click here, then transitions to a full link once the closing ) arrives. Custom component override: For full control over how incomplete links render, override the a component:
<Streamdown
  components={{
    a: ({ href, children, ...props }) => {
      const isIncomplete = href === 'streamdown:incomplete-link';

      if (isIncomplete) {
        return <span className="text-muted-foreground">{children}</span>;
      }

      return (
        <a href={href} target="_blank" rel="noreferrer" {...props}>
          {children}
        </a>
      );
    },
  }}
>
  {markdown}
</Streamdown>

Images

For incomplete image syntax, Streamdown removes the element entirely rather than showing a broken placeholder:
![Alt text that's incomplete
This prevents visual clutter while the AI is still generating the alt text or URL.

Mathematical expressions

Block-level KaTeX expressions are completed:
$$
E = mc^2
The closing $$ is appended so KaTeX can render the expression immediately.

Configuration

The parseIncompleteMarkdown prop

Unterminated block parsing is enabled by default. You can disable it if needed:
import { Streamdown } from 'streamdown';

export default function Page() {
  return (
    <Streamdown parseIncompleteMarkdown={false}>
      {markdown}
    </Streamdown>
  );
}
Disabling parseIncompleteMarkdown means incomplete syntax is passed directly to the renderer. Users will see raw Markdown characters like ** and ` while the AI is still streaming.
When mode="static" is set, remend is also skipped automatically since the content is already complete.

Passing options to remend

You can configure which completions are active via the remend prop:
<Streamdown remend={{ links: false, katex: false }}>
  {markdown}
</Streamdown>
All available options are documented on the remend page.

Smart behavior

Remend includes a set of rules to avoid false positives:

Code block awareness

Formatting within complete code fences is left untouched:
```python
def foo():
    # This **won't** be treated as incomplete bold
    return "bar"
```

Math block protection

Underscores within math blocks are not treated as italic markers:
$$
x_i = y_j
$$

List item detection

The parser won’t close formatting markers that appear at the start of list items:
- **
- Item with bold marker only

Word-internal characters

Asterisks and underscores within words are preserved:
const user_name = "john_doe";
hello*world
This ensures product codes, variable names, and mathematical expressions with asterisks are not mistakenly treated as italic formatting.

Streaming examples

Bold text streaming

As content arrives:
ReceivedRenders as
**This(nothing — too short)
**This is bolThis is bol
**This is bold**This is bold
With default linkMode: 'protocol':
ReceivedRenders as
[Cli(nothing)
[Click hereClick here
[Click here](https://Click here
[Click here](https://example.com)Click here

Inline code streaming

ReceivedRenders as
`constconst
`const foo =const foo =
`const foo = "bar"`const foo = "bar"

Performance

Remend is optimized for high-frequency streaming updates:
  • Direct string iteration — avoids regex splits and allocations
  • ASCII fast-path — optimized character checking for common characters
  • Early returns — stops processing when conditions aren’t met
  • Zero dependencies — pure TypeScript with no runtime overhead
  • Block-level processing — Streamdown splits content into blocks for parallel rendering
For more detail on the standalone remend package, see the remend page.