---
title: "Comark vs Markstream"
description: "Markstream and Comark both stream AI Markdown in several frameworks. Compare incomplete-syntax handling, components in content, string renderers, security defaults, and renderer maturity."
canonical_url: "https://comark.dev/compare/comark-vs-markstream"
---
# Comark vs Markstream

> Markstream and Comark both stream AI Markdown in several frameworks. Compare incomplete-syntax handling, components in content, string renderers, security defaults, and renderer maturity.

**TL;DR**: [Markstream](https://github.com/Simon-He95/markstream-vue) is a family of streaming Markdown renderers for AI chat, with packages for Vue 3, Vue 2, React, Svelte, and Angular. Comark solves the same problem with one parser and one serializable document for every renderer. It also adds component syntax with props and slots, plus HTML and terminal output.

## At a glance

|                       | Comark                                                                                                                          | Markstream                                                         |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Frameworks            | Vue 3, React, Svelte 5, Angular 17+, Nuxt                                                                                       | Vue 3, Vue 2, React, Svelte 5, Angular 20+, Nuxt                   |
| String output         | [HTML](https://comark.dev/rendering/html) and [ANSI](https://comark.dev/rendering/ansi) renderers                               | No, framework renderers only                                       |
| Renderer maturity     | All packages share one pre-1.0 version, none labeled alpha or beta                                                              | Vue stable, React beta, Svelte beta or experimental, Angular alpha |
| Incomplete Markdown   | [`autoClose`](https://comark.dev/reference/auto-close) completes the syntax                                                     | Loading nodes until the `final` prop is set                        |
| Components in content | `::flight-card{from="CDG"}` with props and slots                                                                                | HTML-like custom tags such as `<thinking>`                         |
| Parser output         | `MarkdownDocument` with tuple nodes, JSON-safe                                                                                  | `ParsedNode[]` from `stream-markdown-parser`                       |
| Raw HTML by default   | Kept as written, add the [security plugin](https://comark.dev/plugins/built-in/security) with `blockedTags` for untrusted input | Safe HTML policy on by default                                     |
| Long documents        | Incremental parse in `streaming` mode                                                                                           | Virtualization, batching, and workers                              |
| Diagrams              | [Mermaid plugin](https://comark.dev/plugins/built-in/mermaid)                                                                   | Progressive Mermaid, D2, and AntV Infographic                      |

## Same problem, two designs

Both projects render Markdown that an LLM sends token by token. Both are based on markdown-it: Markstream uses `markdown-it-ts`, and Comark uses markdown-exit, a TypeScript rewrite of markdown-it.

The design differs after parsing. Markstream gives each framework a renderer package with chat-oriented presets, such as `mode="chat"`. Comark parses into one [document model](https://comark.dev/getting-started/document-model) that every renderer consumes the same way.

::code-group
```vue [Markstream]
<script setup lang="ts">
import MarkdownRender from 'markstream-vue'
import 'markstream-vue/index.css'

defineProps<{ content: string, isDone: boolean }>()
</script>

<template>
  <MarkdownRender mode="chat" :content="content" :final="isDone" />
</template>
```

```vue [Comark]
<script setup lang="ts">
import { Markdown } from '@comark/vue'

defineProps<{ content: string, isStreaming: boolean }>()
</script>

<template>
  <Suspense>
    <Markdown :value="content" :streaming="isStreaming" caret />
  </Suspense>
</template>
```
::

## Incomplete Markdown: completed syntax or loading states

Markstream marks unclosed constructs as mid-state `loading` nodes. Your components can show a placeholder until the closing syntax arrives or the `final` prop is set.

Comark completes the syntax instead. On every parse, [`autoClose`](https://comark.dev/reference/auto-close) closes unterminated bold, inline code, links, tables, and components. An open code fence renders as a growing code block. In `streaming` mode, Comark also drops a half-typed opener at the end and reuses the output of completed blocks. Each frame is a complete document, so your components receive normal props and children.

## Components with props and slots

Markstream renders custom components through HTML-like tags. You register a component for a tag such as `<thinking>`, and the parser emits a node with the tag body and raw attributes.

Comark makes components part of the Markdown syntax. A model or an author can write typed props, nested Markdown, and named slots:

```mdc
::flight-card{from="CDG" to="JFK" price="342"}
Departs at 8:15 AM, nonstop.

#footer
Prices include taxes.
::
```

The parser produces data: `['flight-card', { from: 'CDG', to: 'JFK', price: '342' }, ...]`. The renderer maps the tag to a component you registered, so nothing in the Markdown executes code. See [component syntax](https://comark.dev/syntax/components) and [generative UI](https://comark.dev/use-cases/generative-ui).

## One document, every renderer

`parseMarkdown()` returns a JSON-safe `MarkdownDocument`. You can parse on the server, cache the result, and render it in any framework:

```ts [server.ts]
import { parseMarkdown } from 'comark'
import { renderHtmlFromDocument } from '@comark/html'

const document = await parseMarkdown(markdown)
const html = await renderHtmlFromDocument(document) // emails, RSS, static pages
```

The same document renders with `<MarkdownDocument>` in [Vue](https://comark.dev/rendering/vue), [React](https://comark.dev/rendering/react), and [Svelte](https://comark.dev/rendering/svelte). It renders with `<comark-markdown-document>` in [Angular](https://comark.dev/rendering/angular) and with `renderAnsiFromDocument()` in a [terminal](https://comark.dev/rendering/ansi). Markstream can also pre-parse on a server or in a worker and pass `nodes` to a renderer. It has no HTML string or terminal output.

## Security defaults

Markstream applies `htmlPolicy="safe"` by default. It blocks tags such as `script`, `iframe`, and `form`, removes `on*` attributes, and filters dangerous URL protocols, also during streaming.

Comark doesn't create a link from Markdown link syntax with a `javascript:`, `vbscript:`, or `data:text/html` URL. Everything else is kept as written. Raw HTML, attributes, and component props can contain `<script>` tags, `on*` handlers, and `javascript:` URLs.

For LLM output or user content, add the [security plugin](https://comark.dev/plugins/built-in/security). It removes event handlers and unsafe URLs, and drops the tags that you list in `blockedTags`:

```ts
import security from '@comark/vue/plugins/security'

const plugins = [
  security({ blockedTags: ['script', 'iframe', 'embed', 'form', 'object', 'style'] }),
]
```

## What Markstream does well

- **Framework reach**: Markstream supports Vue 2.6 and 2.7 through a compatibility port. Comark has no Vue 2 renderer.
- **Long responses**: virtualization with `max-live-nodes`, render batching, and worker-based parsing keep long documents responsive. Comark has no virtualization.
- **Chat built-ins**: progressive Mermaid in a worker, KaTeX in a worker, D2, AntV Infographic, typewriter and fade effects, and enhanced code and diff blocks.
- **Safe by default**: the safe HTML policy protects untrusted output without extra setup.

If you build a Vue chat UI with very long responses, or you must support Vue 2, Markstream is a strong choice.

## FAQ

::accordion
  :::accordion-item{label="Which one fits a multi-framework AI product?"}
  Pick Comark if you need one content format across several frameworks, server-rendered HTML, or terminal output. Pick Markstream if you need Vue 2 or virtualization for very long responses. See the [AI chat roundup](https://comark.dev/compare/markdown-libraries-for-ai-chat).
  :::

  :::accordion-item{label="Is Comark safe for LLM output without configuration?"}
  No. By default, Comark keeps raw HTML tags, event handler attributes, and unsafe URLs in HTML and component props. Add the [security plugin](https://comark.dev/plugins/built-in/security) with `blockedTags` for untrusted input.
  :::

  :::accordion-item{label="Can I use markdown-it plugins in both?"}
  Comark registers markdown-it plugins through `markdownItPlugins`, as long as they add parsing rules. Plugins that only change `md.renderer` output have no effect. See [markdown-it plugins](https://comark.dev/plugins/custom/markdown-it).
  :::

  :::accordion-item{label="How do I stream with the Vercel AI SDK?"}
  Pass each text part to `<Markdown>` and set `streaming` while `part.state === 'streaming'`. See [AI chat streaming](https://comark.dev/use-cases/ai-chat-streaming) and the [Nuxt AI SDK example](https://comark.dev/examples/ai/nuxt-ai-sdk).
  :::
::

---

- [Streaming API](https://comark.dev/reference/auto-close)
- [Playground](https://comark.dev/stream)


## Sitemap

See the full [sitemap](https://comark.dev/sitemap.md) for all pages.
