Choosing a Markdown Library for AI Chat in 2026
TL;DR: For a React-only chat where the model writes standard Markdown, start with Streamdown. If the model must place components, or you ship Vue, Svelte, Angular, or a terminal UI, use Comark. For Vue 2 or very long documents that need virtualization, look at Markstream. For a Svelte-only app built on marked extensions, use @humanspeak/svelte-markdown.
This guide is part of Comark's documentation. Each claim about another library links to that library's repository or documentation, so you can check it.
What an AI chat renderer needs
A model sends its answer a few characters at a time. A renderer built for complete documents re-parses the whole text on each update. Between updates, it shows broken output. Five questions separate the options:
- Incomplete syntax: what does the reader see while
**bold, a code fence, a link, or a table is still open? The renderer can complete the syntax, hide it, or show it raw. - Re-render cost: does each new token re-parse and re-render the whole message, or only the last block? This matters for long answers.
- Components in the output: can the model place a chart, a product card, or a tool result? Can that component take props and nested content?
- Security of model output: model output is untrusted input. Check what happens to raw
<script>,on*handlers, andjavascript:links by default. - Framework: a renderer for one framework locks the chat UI to it. Check which frameworks have a stable release.
The options
Streamdown
Streamdown is a React component library by Vercel, built on unified and remark. It positions itself as a drop-in replacement for react-markdown, and it powers the AI SDK's AI Elements.
- Incomplete syntax: its
remendpreprocessor completes unterminated syntax at the string level, before parsing. Incomplete links point tostreamdown:incomplete-link. - Re-render cost: it splits the text into blocks and memoizes them.
- Chat features: animation, carets, copy and download buttons, table export, and a confirmation modal for links.
- Components: a react-markdown style
componentsmap. Custom HTML tags needallowedTags, or the sanitizer strips them. - Security: rehype-sanitize with the GitHub schema, plus rehype-harden, are on by default.
- Tradeoffs: React only. Code highlighting, math, Mermaid, and CJK support are separate
@streamdown/*plugins.
Markstream
Markstream has packages for Vue 3, React, Svelte 5, Angular 20+, and Vue 2. Its parser is built on markdown-it-ts.
- Incomplete syntax: unclosed constructs render as "loading" nodes until you set the
finalprop. - Re-render cost: a token cache, render modes, batching, and virtualization with
max-live-nodeshelp with long documents. - Chat features: smooth streaming, typewriter and fade effects, Mermaid and KaTeX in a worker, and optional D2, AntV Infographic, and diff blocks.
- Components:
setCustomComponents()replaces node renderers. Custom HTML-like tags such as<thinking>can map to components. - Security:
htmlPolicy="safe"is the default, and content is sanitized mid-stream too. - Tradeoffs: its own docs list Vue as stable, React as beta, Svelte as beta or experimental, and Angular as alpha. Its component mechanisms are node overrides, custom tags, and placeholders such as
[[CALLOUT:warning title="..."]], not a syntax with named slots.
Comark
Comark is an open-source Markdown parser and renderer with components, attributes and plugins. It streams AI output to React, Vue, Svelte, Angular, HTML and the terminal.
- Incomplete syntax:
autoClosecompletes unterminated syntax on every parse, including components, tables, and frontmatter. Incomplete links point tocomark:incomplete-link. - Re-render cost: the parser's
streamingoption reuses the previous output when the new text appends to the old text. It also drops a half-typed opener at the end. The Vue, Svelte, and Angular renderers pass theirstreamingprop to the parser. - Components: the model writes component syntax such as
::flight-card{from="CDG"}, with props and named slots. The renderer mounts only components you registered. - Tradeoffs: the default
htmlplugin keeps raw<script>, so add the security plugin with ablockedTagslist for model output. The parser bundle is larger than marked or markdown-it. There is no remark or rehype plugin support.
@humanspeak/svelte-markdown
@humanspeak/svelte-markdown is the Svelte 5 successor to the original svelte-markdown. It is built on marked and htmlparser2.
- Incomplete syntax: it buffers half-open tags. Its documentation doesn't describe completing unterminated inline syntax.
- Re-render cost: it re-lexes only the open block and coalesces updates once per animation frame.
writeChunk()accepts out-of-order chunks. - Components: any HTML tag, such as
<thinking>, can map to a component throughrenderers.html. - Security: a URL allowlist,
on*andsrcdocstripping, and escapedscriptandstyleare on by default. Its docs list gaps, for exampleiframe,form, andembedstill render. - Tradeoffs: Svelte only. Streaming turns off when you use an async extension such as Mermaid.
markdown-to-jsx
markdown-to-jsx has zero dependencies. It outputs React, React Native, Solid, Vue, HTML, and Markdown.
- Incomplete syntax: the
optimizeForStreamingoption hides incomplete syntax until the closing delimiter arrives. It is off by default. - Components: PascalCase JSX tags in the content render as your components, with parsed array, object, and boolean props.
- Security: its tag filter and raw HTML sanitization are on by default.
- Tradeoffs: there is no plugin system; you customize output with
overridesandrenderRule.
Plain parsers (marked, markdown-it, remark)
marked, markdown-it, and remark are mature, fast parsers with large plugin ecosystems. None of them handles incomplete Markdown. For a chat UI, you add your own completion and block splitting.
- marked returns an HTML string and doesn't sanitize it. Its docs recommend a library such as DOMPurify. See Comark vs marked.
- markdown-it disables raw HTML by default and rejects
javascript:links. See Comark vs markdown-it. - remark drops raw HTML unless you opt in, and recommends
rehype-sanitize. See Comark vs remark and Comark vs react-markdown.
Comparison
| Library | Frameworks | Incomplete Markdown | Components in output | Sanitization default |
|---|---|---|---|---|
| Streamdown | React | remend completes syntax before parsing | components map; custom tags need allowedTags | rehype-sanitize (GitHub schema) and rehype-harden |
| Markstream | Vue 3, React (beta), Svelte 5 (beta), Angular 20+ (alpha), Vue 2 | Loading nodes until final | setCustomComponents(), custom HTML-like tags | htmlPolicy="safe" |
| Comark | Vue, React, Svelte, Angular, HTML, ANSI | autoClose completes syntax on every parse | ::component{props} with named slots | Unsafe Markdown link URLs aren't linked; raw HTML, including <script>, is kept until you add the security plugin with blockedTags |
| @humanspeak/svelte-markdown | Svelte 5 | Re-lexes the open block, buffers half-open tags | renderers.html.<tag> for any tag | URL allowlist, on* and srcdoc stripped, script and style escaped |
| markdown-to-jsx | React, React Native, Solid, Vue, HTML | optimizeForStreaming hides it (opt-in) | PascalCase JSX tags with parsed props | Tag filter and raw HTML sanitization on |
| marked, markdown-it, remark | Any (HTML string or AST) | None | None built in | marked: none; markdown-it and remark: raw HTML off |
How to choose
Start with the constraint that you can't change, usually the framework, and then check the other needs:
- React-only chat, standard Markdown, no component syntax: Streamdown fits. Animation, carets, and copy buttons are built in. See Comark vs Streamdown.
- Components in model output: Comark fits. The model writes
::component{props}text, and the renderer mounts only the components you registered. See generative UI. - Vue, Svelte, Angular, or terminal output: Comark has a renderer for each. One parsed document renders in all of them.
- Vue 2, or very long documents that need virtualization: Markstream fits. See Comark vs Markstream.
- Svelte-only, with marked extensions: @humanspeak/svelte-markdown fits. See Comark vs svelte-markdown.
- React Native or Solid: markdown-to-jsx has entry points for both. See Comark vs markdown-to-jsx.
- Server-side HTML only, no streaming: a plain parser with a sanitizer can be enough.
Whatever you pick, treat the model's output as untrusted input. Check the sanitization defaults in the table, and test with a response that contains <script> and a javascript: link.
FAQ
remend is a separate preprocessor that completes unterminated syntax in a string. Comark has the same role in autoCloseMarkdown() from comark, which you can also call on its own. See auto-close.<script> or a javascript: link. Treat every response as untrusted input.Next steps
- Build a streaming chat UI in your framework with AI chat streaming.
- Let the model render your components with generative UI.
- Watch
autoClosecomplete partial Markdown in the streaming playground.
Emails and RSS
Turn Markdown with components into HTML strings for emails and RSS feeds with @comark/html, including inline-styled components and CDATA output.
Comark vs MDX
Comark parses component syntax at runtime with no build step, while MDX compiles Markdown to JSX at build time. Compare syntax, architecture, framework support, and streaming.