---
title: "Comark vs remark and unified"
description: "remark and unified transform Markdown through mdast and hast syntax trees with a large plugin ecosystem. Comark parses into a compact document with component syntax, streaming, and multi-framework renderers."
canonical_url: "https://comark.dev/compare/comark-vs-remark"
---
# Comark vs remark and unified

> remark and unified transform Markdown through mdast and hast syntax trees with a large plugin ecosystem. Comark parses into a compact document with component syntax, streaming, and multi-framework renderers.

**TL;DR**: [remark](https://github.com/remarkjs/remark) is the Markdown part of [unified](https://github.com/unifiedjs/unified). It parses Markdown into mdast, transforms it with plugins, and converts it to HTML through hast. It's the foundation of MDX and react-markdown. Comark parses into a compact tuple document with component syntax, completes incomplete Markdown for streaming, and renders natively in Vue, React, Svelte, and Angular. Comark doesn't run remark or rehype plugins.

## At a glance

|                              | Comark                                                                                                                   | remark and unified                                      |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
| Syntax tree                  | `MarkdownDocument` with tuple nodes                                                                                      | mdast for Markdown, hast for HTML                       |
| Spec compliance              | CommonMark and GFM with [documented differences](https://comark.dev/syntax/markdown#differences-from-commonmark-and-gfm) | Strict CommonMark, GFM with `remark-gfm`                |
| Plugins                      | Comark plugins and [markdown-it plugins](https://comark.dev/plugins/custom/markdown-it)                                  | remark and rehype plugins                               |
| Tree transforms              | `visit()` from `comark/utils` in a `post` hook                                                                           | `unist-util-visit` and other unist utilities            |
| Custom components in content | Yes, `::alert{type="info"}`                                                                                              | Through extensions such as MDX or directives            |
| Incomplete Markdown          | [`autoClose`](https://comark.dev/reference/auto-close) completes the syntax                                              | No handling in the core                                 |
| Renderers                    | Vue, React, Svelte, Angular, Nuxt, HTML, ANSI                                                                            | HTML with `rehype-stringify`, React with react-markdown |
| Raw HTML by default          | Kept as written                                                                                                          | Dropped unless you set `allowDangerousHtml`             |
| Back to Markdown             | [`renderMarkdown()`](https://comark.dev/reference/render)                                                                | `remark-stringify`                                      |

## Two syntax trees or one document

unified works in stages. `remark-parse` creates mdast, remark plugins transform it, and `remark-rehype` converts it to hast. Then rehype plugins run, and `rehype-stringify` outputs HTML:

```ts
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkGfm from 'remark-gfm'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'

const file = await unified()
  .use(remarkParse)
  .use(remarkGfm)
  .use(remarkRehype)
  .use(rehypeStringify)
  .process('# Hello **World**')
```

Comark has one document. GFM tables, strikethrough, task lists, frontmatter, components, and raw HTML are parsed by default. Every renderer reads the same nodes:

```ts
import { parseMarkdown } from 'comark'

const document = await parseMarkdown('# Hello **World**')
// [['h1', { id: 'hello-world' }, 'Hello ', ['strong', {}, 'World']]]
```

mdast nodes are objects with `type`, `children`, and fields such as `depth`. Comark nodes are arrays: a tag, an attributes object, then children. Text nodes are plain strings. The document is JSON-safe, so you can cache it or send it to a client. See the [document model](https://comark.dev/getting-started/document-model).

## Transform the tree

Both projects let you change the tree after parsing. In remark, a plugin returns a transformer that visits mdast nodes. In Comark, a plugin uses `visit()` in a `post` hook.

::code-group
```ts [remark]
import { visit } from 'unist-util-visit'

export default function remarkWordCount() {
  return (tree, file) => {
    let count = 0
    visit(tree, 'text', (node) => {
      count += node.value.split(/\s+/).filter(Boolean).length
    })
    file.data.wordCount = count
  }
}
```

```ts [Comark]
import { defineComarkPlugin } from 'comark/parse'
import { visit } from 'comark/utils'

export default defineComarkPlugin(() => ({
  name: 'word-count',
  post(state) {
    let count = 0
    visit(state.tree,
      (node) => typeof node === 'string',
      (node) => { count += (node as string).split(/\s+/).filter(Boolean).length }
    )
    state.tree.meta.wordCount = count
  },
}))
```
::

remark plugins don't run in Comark, because the trees have different shapes. To port one, rewrite its transform with `visit()`, or look for a markdown-it plugin that adds the same syntax. See the [AST API](https://comark.dev/plugins/custom/ast-api) and the [plugin API](https://comark.dev/plugins/custom/plugin-api).

## Components and streaming

In the unified ecosystem, components in Markdown usually mean MDX, which compiles JSX and JavaScript expressions. Comark components are data: `::alert{type="info"}` becomes `['alert', { type: 'info' }, ...]`. Your app maps the tag to a component, and nothing in the Markdown executes code. See [Comark vs MDX](https://comark.dev/compare/comark-vs-mdx).

remark expects complete input. For LLM output, Comark closes unterminated bold, links, tables, and components on every parse, and renders an open code fence as a growing code block. See [AI chat streaming](https://comark.dev/use-cases/ai-chat-streaming).

## Security defaults

remark is cautious by default. `remark-rehype` drops raw HTML unless you set `allowDangerousHtml` and add `rehype-raw`. The unified docs recommend `rehype-sanitize` for untrusted input.

Comark keeps raw HTML, attributes, and component props as written. Markdown link syntax with a `javascript:`, `vbscript:`, or `data:text/html` URL doesn't become a link. For untrusted input, add the [security plugin](https://comark.dev/plugins/built-in/security) with a `blockedTags` list.

## What remark does well

- **Spec compliance**: remark follows CommonMark strictly. Comark changes some output on purpose. See [Differences from CommonMark and GFM](https://comark.dev/syntax/markdown#differences-from-commonmark-and-gfm).
- **Plugin ecosystem**: lint rules, tables of contents, math, MDX, and many rehype transforms. Comark has a smaller set of [built-in plugins](https://comark.dev/plugins).
- **Tooling beyond rendering**: remark is also used to lint and format Markdown, not only to convert it to HTML.
- **Safe defaults**: raw HTML is dropped unless you allow it.

If your pipeline depends on remark or rehype plugins, or you need strict spec output, stay with unified.

## FAQ

::accordion
  :::accordion-item{label="Can I use remark or rehype plugins in Comark?"}
  No. Comark uses markdown-exit, a TypeScript rewrite of markdown-it, and its own document model. Use [markdown-it plugins](https://comark.dev/plugins/custom/markdown-it) for new syntax and [`visit()`](https://comark.dev/plugins/custom/ast-api) for tree transforms.
  :::

  :::accordion-item{label="Can I convert an mdast tree to a Comark document?"}
  Comark has no converter for mdast. Parse the Markdown source with `parseMarkdown()` instead.
  :::

  :::accordion-item{label="How does Comark compare to react-markdown?"}
  react-markdown is built on remark. For a React-focused comparison, read [Comark vs react-markdown](https://comark.dev/compare/comark-vs-react-markdown).
  :::

  :::accordion-item{label="Does Comark support frontmatter like remark-frontmatter?"}
  Yes, by default. `parseMarkdown()` returns the YAML data in `document.frontmatter`. See [frontmatter](https://comark.dev/syntax/frontmatter).
  :::
::

---

- [AST API](https://comark.dev/plugins/custom/ast-api)
- [Playground](https://comark.dev/play)


## Sitemap

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